The team decides to start with the Valet Parking examples from Table 1.11 of the parking lot story. The team chooses Cucumber1 to automate the tests. Cucumber uses Ruby to glue together the data representation of the examples and the system under test. Each set of tests in Cucumber is called a feature. A feature goes into a text file on its own.
In order to automate a test in Cucumber, we need a combination of features that holds our data, some definition of test steps to interact with the application under test, as well as a set of environmental settings.
Tony has the diagram shown in Figure 2.1 in mind for the overall architecture.
The examples on the top are the ones the team identified at the workshop. Tony is now going to feed them to Cucumber. Cucumber will need some kind of glue code to exercise the application under test. The glue code can be split up into step definitions, support code, and third-party libraries like Selenium to drive the browser from the code.
Tony plans to grow a library of support code for the parking lot calculator. The glue code from the step definitions will use this library.
The automation setup will use Selenium.2 Selenium drives a browser from the automation code. The automation code can then interact with webpages and check for correct values like the calculated parking costs. The team has set up a continuous integration system with a headless Selenium server3 to which the tests will connect during the build.
Tony picks the 30-minute examples first. He starts to describe the features for Valet Parking in the file Valet.feature
. Tony writes his first test close to the table they ended up with in the workshop (see Listing 2.1).
1 Feature: Valet Parking feature
2 The parking lot calculator calculates costs for Valet
Parking.
3
4 Scenario: Calculate Valet Parking Cost for half an hour
5 When I park my car in the Valet Parking Lot for 30 minutes
6 Then I will have to pay $ 12.00
The Valet Parking feature includes thus far the first test for 30 minutes. The expected parking rate is $12.00 as discussed during the workshop. In Listing 2.1, the first line refers to the name of the feature that we’re about to test. The second line describes this feature in prose terms. Cucumber displays this description on the console. Tony usually puts in anything into this description that communicates his intention to a future test author, also being aware that this might be himself in a few months time.
On line 4 he named his first test “Calculate Valet Parking Cost for half an hour.” Since the first test just focuses on the parking duration of 30 minutes, he named it appropriately. The lines 5-6 use the keywords When and Then in order to express two distinct phases of one test.
The When-keyword describes any actions that should be taken in order to trigger the system under test to operate. This might include calling a function or pressing a button.
The name of the parking lot and the parking duration are in the When-keyword parameters. Cucumber will parse these and feed them into the system so that the application can calculate the parking costs.
The Then-keyword describes any post conditions of the test after it executed the application. Any desired effects of the application are put here. In the particular test Tony included a check for the calculated parking costs.
At this point Tony saves the file Valet.feature
and runs it through Cucumber by issuing the command cucumber Valet.feature
. Tony gets the output shown in Listing 2.2. It shows the first scenario that Tony just entered into his text editor. In line 8 it tells Tony that Cucumber tried to run one scenario, from which one was undefined. Cucumber identified also two undefined steps in line 9. Starting in line 12, Tony gets a hint on how to implement the missing steps.
1 Feature: Valet Parking feature
2 The parking lot calculator can calculate costs for Valet
Parking.
3
4 Scenario: Calculate Valet Parking Cost
# Valet.feature:4
5 When I park my car in the Valet Parking Lot for 30 minutes
# Valet.feature:5
6 Then I will have to pay $ 12.00
# Valet.feature:6
7
8 1 scenario (1 undefined)
9 2 steps (2 undefined)
10 0m0.001s
11
12 You can implement step definitions for undefined steps with
these snippets:
13
14 When /^I park my car in the Valet Parking Lot for (d+)
minutes$/ do |arg1|
15 pending # express the regexp above with the code you wish
you had
16 end
17
18 Then /^I will have to pay $ (d+).(d+)$/ do |arg1, arg2|
19 pending # express the regexp above with the code you wish
you had
20 end
21
22 If you want snippets in a different programming language, just
make sure a file
23 with the appropriate file extension exists where cucumber
looks for step definitions.
As prompted by Cucumber, Tony creates a file for the step definitions by copying and pasting the provided example from the shell output to a new file called Valet steps.rb
. In order to separate the test data from the glue code that exercises the instructions on the system under test, Tony puts the support code into a file in a new folder called step definitions
.
Tony makes some adaptions to the suggested method stubs. These adaptations will become handy later when he will extend the first example with different parking durations. The result is listed in Listing 2.3.
Cucumber can parse some variables from the textual examples. Tony used this for the duration and the price. pending
is a keyword that Cucumber will recognize, and report back, that this test is currently pending and probably just evolving. When Tony re-executes the test with the step definitions now in place, he gets the output shown in Listing 2.4.
1 When /^I park my car in the Valet Parking Lot for (.*)$/ do
|duration|
2 pending
3 end
4
5 Then /^I will have to pay (.*)$/ do |price|
6 pending
7 end
1 Feature: Valet Parking feature
2 The parking lot calculator can calculate costs for Valet
Parking.
3
4 Scenario: Calculate Valet Parking Cost
# Valet.feature:4
5 When I park my car in the Valet Parking Lot for 30 minutes
# step_definitions/Valet_steps.rb:1
6 TODO (Cucumber::Pending)
7 ./step_definitions/Valet_steps.rb:2:in '/^I park my car
in the Valet Parking Lot for (.*)$/'
8 Valet.feature:5:in 'When I park my car in the Valet
Parking Lot for 30 minutes'
9 Then I will have to pay $ 12.00
# step_definitions/Valet_steps.rb:5
10
11 1 scenario (1 pending)
12 2 steps (1 skipped, 1 pending)
13 0m0.002s
In order to execute the system under test, Tony also needs to configure the driver for the web browser, Selenium. Selenium comes with a server component and a client library that his support code can use to drive the browser and navigate on web pages. Tony writes this support code into its own file env.rb
, so that he can keep it separate from the glue code that exercises the system under test. He places the file into a newly created subfolder etc
(see the folder layout in Figure 2.2). Everything he needs to have in place for that is listed in Listing 2.5.
1 require 'rubygems'
2 gem 'selenium-client'
3 require 'selenium/client'
4 require 'spec/expectations'
5 require 'lib/parkcalc'
6
7 # before all
8 selenium_driver = Selenium::Client::Driver.new
9 :host => 'localhost',
10 :port => 4444,
11 :browser => '*firefox',
12 :url => 'http://www.shino.de/parkcalc',
13 :timeout_in_second => 60
14 selenium_driver.start_new_browser_session
15 $parkcalc = ParkCalcPage.new(selenium_driver)
16
17 # after all
18 at_exit do
19 selenium_driver.close_current_browser_session
20 end
This support code makes use of the Selenium library, starts a browser, and opens the ParkCalc page. When all the tests have executed, it will close the browser window again.
The file env.rb
also requires a library for the parking lot calculator, lib/parkcalc
. Tony will grow this library incrementally while developing his tests. The initial contents for this file are shown in Listing 2.6.
1 class ParkCalcPage
2 attr :page
3
4 def initialize(page_handle)
5 @page = page_handle
6 @page.open '/parkcalc'
7 end
8 end
In the initializer the passed-in_page handle is stored into the local page attribute, and the /parkcalc
page is opened. As a first implementation, this does the job of opening the web form using the browser that gets passed in from the setup in env.rb
.
Tony starts to develop the first test step-wise. The first step he has to implement is the filling in of the parking duration parameters into the web interface. Tony does not yet know about the particular implementation details, but he has seen some hand-painted designs for the final layout. The When I park my car in the Valet Parking Lot for <duration>
keyword has two basic steps. First, it needs to select the proper parking lot. Second, it has to fill in values to match the correct parking duration. Tony applies wishful thinking to this particular problem and writes down in Valet steps.rb
what he would like to have as an API in this particular case (see Listing 2.7).
1 When /^I park my car in the Valet Parking Lot for (.*)$/ do
|duration|
2 $parkcalc.select('Valet Parking')
3 $parkcalc.enter_parking_duration(duration)
4 pending
5 end
The first part of the step is denoted on line 2 of Listing 2.7. Tony decided to have a selection mechanism for the parking lot. Depending on the implementation details, this might mean entering the given string into a text field, selecting the proper lot from a combo box, or picking the parking lot from a dropdown menu. Because Tony does not know anything about this yet, he postpones the implementation of this particular detail until the implementation of the ParkCalcPage class.
The second step on line 3 of Listing 2.7 describes that the parking duration is somehow filled in. Again, this might mean putting the text into a text field as is or having a parking start and end date time to calculate for this particular duration and filling it into two date fields. Because the implementation of the user interface is still open, Tony postpones the decision on how to implement the ParkCalcPage class.
In order to indicate that the test step definition is not complete, Tony leaves the pending
keyword at the end of the keyword definition. The future implementor of the keyword will be reminded to edit the step definition once the other two keywords have been implemented.
Tony now informs the future implementor of the ParkCalcPage class about the interface decisions he has just made by providing empty implementations for his two expected methods (see Listing 2.8).
1 class ParkCalcPage
2 attr :page
3
4 def initialize(page_handle)
5 @page = page_handle
6 @page.open '/parkcalc'
7 end
8
9 def select(parking_lot)
10 end
11
12 def enter_parking_duration(duration)
13 end
14
15 end
Tony adds the methods select(parking_lot)
as well as enter_parking_duration(duration)
to the ParkCalcPage. The first one will select the proper parking lot in the future. The second one is responsible for filling in whatever duration the user interface will offer.
Tony now focuses his attention on the verification step in the test he implemented. Similarly to the preparation steps, Tony applies wishful thinking in order to express the final verification of the parking costs. Listing 2.9 shows the changes he makes to step definitions/Valet steps.rb
. The necessary changes to the ParkCalcPage class are shown in Listing 2.10.
1 Then /^I will have to pay (.*)$/ do |price|
2 $parkcalc.parking_costs.should == price
3 pending
4 end
1 def parking_costs
2 return nil
3 end
Tony has now finished the first test as far as he can drive it. He is facing a decision on how to continue. On one hand, he can include the remaining examples from the workshop on Valet Parking. On the other hand, he can now also pair up with a developer to automate the examples in order to drive the development of the feature. A third alternative is to work on the remaining four parking lots and get the first test done in Cucumber. Tony decides to pair up with a developer to implement the first Valet Parking functionality and automate his first test. Tony can get the feedback from his work so far and continue to develop the tests later. Another advantage of this approach will be that Tony is not providing too many failing tests before the implementation starts.
Tony pairs up with Alex to implement and automate the first test. Alex already created a first layout for the website. Alex introduces Tony to his idea.
ALEX
Hi Tony, you’re interested in seeing the parking cost calculator evolve?
TONY
Actually, I wanted to pair up with you in order to get the first test automated.
ALEX
Oh, wonderful. Which one did you start with?
TONY
Valet Parking. I introduced the first test for 30 minutes. I just checked in the first pending example before I came over.
ALEX
OK, let me show you what I’ve done so far.
Alex shows Tony the initial layout of the web page he designed (see Figure 2.3).
ALEX
I designed the different parking lots as a dropdown box. The input date can be entered via a text field directly, or you can use the date picker in a calendar format. The time for the entry and leaving times are free text with a radio button to indicate AM or PM. The estimated parking costs will be shown when the Calculate button is pressed.
TONY
This looks good. Now, the following step definitions are pending, and we need to hook up to them now. Look at these.
ALEX
OK, this doesn’t look too complicated. Let’s start with the selection of the parking lot from the dropdown. I used the id “ParkingLot” here. So, selecting the proper value from the dropdown is a single step, like this.
Alex implements the select
method in lib/parkcalc.rb
as in Listing 2.11.
1 def select(parking_lot)
2 @page.select 'ParkingLot', parking_lot
3 end
TONY
OK, this seems intuitive. I select the passed-in parameter from the element with the id “ParkingLot.” Nice. What about entering the parking duration?
ALEX
This may require a bit more thought. Let’s use a hash in Ruby for this. In the future you can extend the value for entry and exit date and time then model all the different durations that you will need. If I remember the pieces I saw in the examples from the workshop correctly, there will be quite a few different values for this.
TONY
How do we do this?
ALEX
We will basically do a lookup from the duration string you pass into this function for the actual values for entry date, entry time, entry AM or PM, exit date, exit time, and exit AM or PM and pass them into the web page. These six values represent the six fields in the form I built. But let’s define the hashmap first.
Alex creates a duration map at the top of the ParkCalcPage class (see Listing 2.12).
TONY
The two @’s there are indicating that durationMap is a class variable. Is this correct?
1 @@durationMap = {
2 '30 minutes' => ['05/04/2010', '12:00', 'AM', '05/04/2010'
, '12:30', 'AM']
3 }
ALEX
Right. We will now use this hashmap to get the six values we are interested in. Let me show you how we will get the values out of the map.
Alex starts to implement the enter_parking_duration
function (see Listing 2.13).
1 def enter_parking_duration(duration)
2 startingDate, startingTime, startingTimeAMPM, leavingDate,
leavingTime, leavingTimeAMPM = @@durationMap[duration]
3 end
ALEX
Now, let’s use these values and put them into the form. Let’s start with the starting dates and times.
Alex extends the method that enters the parking duration with the changes in Listing 2.14.
1 def enter_parking_duration(duration)
2 startingDate, startingTime, startingTimeAMPM, leavingDate,
leavingTime, leavingTimeAMPM = @@durationMap[duration]
3 @page.type 'StartingDate', startingDate
4 @page.type 'StartingTime', startingTime
5 @page.click "//input[@name='StartingTimeAMPM' and @value
='%s']" % startingTimeAMPM
6 end
TONY
Can you explain this? I have trouble understanding the last line.
ALEX
Let me explain this in a bit more detail. First we get our six parameters out of the hashmap using the provided duration as a key. Then we fill in the starting date and time accordingly.
Yeah, that’s intuitive to me. But what’s that gibberish in the last line doing there?
ALEX
This is how I locate values of the radio buttons. It’s an xpath entry, which expresses where the radio element is located and what its value is. It tells the driver to click on the input, whose name is “StartingTimeAMPM” and whose value matches the one provided.
TONY
I tend to put this at some other place. This looks too technical to me to be held in this otherwise more abstract method.
ALEX
I think you’re correct, Tony. But let’s write it down in our notes and finish this function first. Filling the end date and time is still missing. This is similar to the starting times. But let’s see first if this thing works now.
Alex starts executing the test, and both Alex and Tony watch a browser window pop up, opening the parking lot calculator page, filling in the values for the parking lot and the starting date and time. In the end the browser is closed, and the result is shown.
TONY
This looks good. Let’s continue with the duration. We still need to fill in the end date and time.
ALEX
Sure, the code is similar to the one for the start date and time. Let’s copy and paste the code from above and change the variables. We will clean this up after we know that it’s working.
Alex extends the duration method to also fill out the end date and times (see Listing 2.15).
1 def enter_parking_duration(duration)
2 startingDate, startingTime, startingTimeAMPM, leavingDate,
leavingTime, leavingTimeAMPM = @@durationMap[duration]
3 @page.type 'StartingDate', startingDate
4 @page.type 'StartingTime', startingTime
5 @page.click "//input[@name='StartingTimeAMPM' and @value
='%s']" % startingTimeAMPM
6
7 @page.type 'LeavingDate', leavingDate
8 @page.type 'LeavingTime', leavingTime
9 @page.click "//input[@name='LeavingTimeAMPM' and @value='%
s']" % leavingTimeAMPM
10 end
Tony and Alex run these steps and check that the leaving date and time are filled in correctly.
ALEX
All right, this works. Let’s clean this up now. The two code blocks look rather similar. Let’s put them into a method of their own.
Alex and Tony create a new method to fill in either starting or leaving date and times and replace the calls step by step. They verify that their first test still executes properly after each little change. They end up with the code shown in Listing 2.16.
1 def enter_parking_duration(duration)
2 startingDate, startingTime, startingTimeAMPM, leavingDate,
leavingTime, leavingTimeAMPM = @@durationMap[duration]
3 fill_in_date_and_time_for 'Starting', startingDate,
startingTime, startingTimeAMPM
4 fill_in_date_and_time_for 'Leaving', leavingDate,
leavingTime, leavingTimeAMPM
5 end
6
7 def fill_in_date_and_time_for(formPrefix, date, time, ampm)
8 @page.type "%sDate" % formPrefix, date
9 @page.type "%sTime" % formPrefix, time
10 @page.click "//input[@name='%sTimeAMPM' and @value='%s']"
% [ formPrefix, ampm ]
11 end
ALEX
Now, let’s take a look at extracting those gibberish xpath entries.
TONY
Shall we declare a constant expression for these?
ALEX
That was exactly my idea. But I also want to put the other constant strings into a variable, so we can easily change these in the future. Let’s extract one after the other. First let’s get rid of the xpath. We need a name for that variable. What would you call this?
TONY
What about “amPMRadioButtonTemplate”?
That’s OK with me. Shall we then put the time and date strings into timeTemplate and dateTemplate?
TONY
That sounds good to me. Let’s also put the prefixes into startingPrefix and leavingPrefix.
ALEX
Right, and I would like to put the lotIdentifier into its own constant as well.
TONY
This looks fine now.
Listing 2.17 shows the final version of the ParkCalcPage class after Alex and Tony have extracted the constants. The initialization steps are complete at this point.
1 class ParkCalcPage
2
3 @@lotIdentifier = 'ParkingLot'
4 @@startingPrefix = 'Starting'
5 @@leavingPrefix = 'Leaving'
6 @@dateTemplate = "%sDate"
7 @@timeTemplate = "%sTime"
8 @@amPMRadioButtonTemplate = "//input[@name='%sTimeAMPM' and
@value='%s']"
9
10 @@durationMap = {
11 '30 minutes' => ['05/04/2010', '12:00', 'AM', '05/04/2010'
, '12:30', 'AM']
12 }
13
14 attr :page
15
16 def initialize(page_handle)
17 @page = page_handle
18 @page.open '/parkcalc'
19 end
20
21 def select(parking_lot)
22 @page.select @@lotIdentifier, parking_lot
23 end
24
25 def enter_parking_duration(duration)
26 startingDate, startingTime, startingTimeAMPM, leavingDate,
leavingTime, leavingTimeAMPM = @@durationMap[duration]
27 fill_in_date_and_time_for @@startingPrefix, startingDate,
startingTime, startingTimeAMPM
28 fill_in_date_and_time_for @@leavingPrefix, leavingDate,
leavingTime, leavingTimeAMPM
29 end
30
31 def fill_in_date_and_time_for(formPrefix, date, time, ampm)
32 @page.type @@dateTemplate % formPrefix, date
33 @page.type @@timeTemplate % formPrefix, time
34 @page.click @@amPMRadioButtonTemplate % [ formPrefix,
ampm ]
35 end
36
37 end
TONY
Now, let’s check the output. We still didn’t click the Calculate button that you put into the form, and we need to find a way to collect the calculated costs from the page.
ALEX
Sure. Let’s start with taking out the pending statement from the step definition, so that our Then definition gets executed.
TONY
Oh, I would have forgotten that. That one could have cost me half a day to search for.
ALEX
Well, this is why we pair, isn’t it?
TONY
While we’re at it, we can take out the pending step from the check that we will be implementing in a few.
ALEX
Yes, right. Now, let’s see how to check for the proper price. First, we need to click the calculate button. I want to add this to the parking_costs
function as a first step before returning the costs. After that we need to wait for the page to load with the new results. Then we get the element holding the cost element and simply return it.
Alex implements the parking_costs
function as in Listing 2.18.
1 def parking_costs
2 @page.click 'Submit'
3 @page.wait_for_page_to_load 10000
4 cost_element = @page.get_text "//tr[td/div[@class='SubHead']
= 'estimated Parking costs']/td/span/b"
5 return cost_element
6 end
TONY
What does the constant 10000 mean there?
ALEX
It’s a timeout value. The driver will wait for 10 seconds until either a new page loads successfully, or the test will fail.
TONY
Now, let’s clean this gibberish up again. I propose to extract the xpath into a constant again.
ALEX
First, let’s see if it already executes correctly. Let’s start the test.
Alex and Tony begin the test, and watch it execute the parking lot calculator, turning the console output in the end green, indicating that everything went well (see Listing 2.19).
1 Feature: Valet Parking feature
2 The parking lot calculator can calculate costs for Valet
Parking.
3
4 Scenario: Calculate Valet Parking Cost
# Valet.feature:4
5 When I park my car in the Valet Parking Lot for 30 minutes
# step_definitions/Valet_steps.rb:1
6 Then I will have to pay $ 12.00
# step_definitions/Valet_steps.rb:6
7
8 1 scenario (1 passed)
9 2 steps (2 passed)
10 0m0.324s
All right, the test passes. Before we break anything, let’s check this thing in, so that we can roll back any changes we make later. Just in case.
ALEX
Good idea.
Alex and Tony check everything they have so far into the version control system.
ALEX
Now back to your proposal. Good idea, but I would like to divide the function up into the two steps first. The first function is to click the Submit button and wait for the page to load. The second function will fetch the calculated costs and simply return them. Let me show you what I mean.
Alex extracts two methods from the initial parking_costs
function (see Listing 2.20).
1 def parking_costs
2 calculate_parking_costs
3 get_parking_costs_from_page
4 end
5
6 def calculate_parking_costs
7 @page.click 'Submit'
8 @page.wait_for_page_to_load 10000
9 end
10
11 def get_parking_costs_from_page
12 @page.get_text "//tr[td/div[@class='SubHead'] = 'estimated
Parking costs']/td/span/b"
13 end
TONY
Now, let’s put the xpath entry describing the location of the cost element into a constant.
ALEX
And while we’re at it, let’s put the name of the Calculate button into a meaningful variable as well.
TONY
Now, a final test run, and we are ready to check in our results into the source code repository.
Tony and Alex watch the test execute. It still passes. They check in the files into the source repository. Listing 2.21 shows the final text of step definitions/Valet steps.rb
and Listing 2.22 shows the final code for lib/parkcalc.rb
.
1 When /^I park my car in the Valet Parking Lot for (.*)$/ do
|duration|
2 $parkcalc.select('Valet Parking')
3 $parkcalc.enter_parking_duration(duration)
4 end
5
6 Then /^I will have to pay (.*)$/ do |price|
7 $parkcalc.parking_costs.should == price
8 end
1 class ParkCalcPage
2
3 @@lotIdentifier = 'ParkingLot'
4 @@startingPrefix = 'Starting'
5 @@leavingPrefix = 'Leaving'
6 @@dateTemplate = "%sDate"
7 @@timeTemplate = "%sTime"
8 @@amPMRadioButtonTemplate = "//input[@name='%sTimeAMPM' and
@value='%s']"
9
10 @@calculateButtonIdentifier = 'Submit'
11 @@costElementLocation = "//tr[td/div[@class='SubHead']
= 'estimated Parking costs']/td/span/b"
12
13 @@durationMap = {
14 '30 minutes' => ['05/04/2010', '12:00', 'AM', '05/04/2010'
, '12:30', 'AM']
15 }
16
17 attr :page
18
19 def initialize(page_handle)
20 @page = page_handle
21 @page.open '/parkcalc'
22 end
23
24 def select(parking_lot)
25 @page.select @@lotIdentifier, parking_lot
26 end
27
28 def enter_parking_duration(duration)
29 startingDate, startingTime, startingTimeAMPM, leavingDate,
leavingTime, leavingTimeAMPM = @@durationMap[duration]
30 fill_in_date_and_time_for @@startingPrefix, startingDate,
startingTime, startingTimeAMPM
31 fill_in_date_and_time_for @@leavingPrefix, leavingDate,
leavingTime, leavingTimeAMPM
32 end
33
34 def fill_in_date_and_time_for(formPrefix, date, time, ampm)
35 @page.type @@dateTemplate % formPrefix, date
36 @page.type @@timeTemplate % formPrefix, time
37 @page.click @@amPMRadioButtonTemplate % [ formPrefix,
ampm ]
38 end
39
40 def parking_costs
41 calculate_parking_costs
42 get_parking_costs_from_page
43 end
44
45 def calculate_parking_costs
46 @page.click @@calculateButtonIdentifier
47 @page.wait_for_page_to_load 10000
48 end
49
50 def get_parking_costs_from_page
51 @page.get_text @@costElementLocation
52 end
53 end
Now, with the first example automated, Tony can easily reuse the steps he created for the first test in order to automate the remaining examples from the workshop. As a first step, he denotes the scenario from Valet.feature
as a scenario outline with examples in a tabulated way. In order to get there, he replaces the duration of 30 minutes with a placeholder, <parking duration>
, he replaces the expected price with the placeholder <parking costs>
, and marks the scenario as a scenario outline. Tony puts the concrete values in a table holding all the examples below the scenario outline. Tony labels the columns with the names of the placeholders. This results in Listing 2.23.
1 Feature: Valet Parking feature
2 The parking lot calculator can calculate costs for Valet
Parking.
3
4 Scenario Outline: Calculate Valet Parking Cost
5 When I park my car in the Valet Parking Lot for <parking
duration>
6 Then I will have to pay <parking costs>
7
8 Examples:
9 | parking duration | parking costs |
10 | 30 minutes | $ 12.00 |
At this point, Tony has started to convert the workshop examples literally to a tabulated format. Tony executes the test to verify that it is still working properly. He gets the output shown in Listing 2.24.
1 Feature: Valet Parking feature
2 The parking lot calculator can calculate costs for Valet
Parking.
3
4 Scenario Outline: Calculate Valet Parking Cost
# Valet.feature:4
5 When I park my car in the Valet Parking Lot for <parking
duration> # step_definitions/Valet_steps.rb:1
6 Then I will have to pay <parking costs>
# step_definitions/Valet_steps.rb:6
7
8 Examples:
9 | parking duration | parking costs |
10 | 30 minutes | $ 12.00 |
11
12 1 scenario (1 passed)
13 2 steps (2 passed)
14 0m0.316s
Now he starts to fill in the remaining examples from the workshop. In the end Tony has put all the examples from the workshop into the tabulated test (see Listing 2.25).
1 Feature: Valet Parking feature
2 The parking lot calculator can calculate costs for Valet
Parking.
3
4 Scenario Outline: Calculate Valet Parking Cost
5 When I park my car in the Valet Parking Lot for <parking
duration>
6 Then I will have to pay <parking costs>
7
8 Examples:
9 | parking duration | parking costs |
10 | 30 minutes | $ 12.00 |
11 | 3 hours | $ 12.00 |
12 | 5 hours | $ 12.00 |
13 | 5 hours 1 minute | $ 18.00 |
14 | 12 hours | $ 18.00 |
15 | 24 hours | $ 18.00 |
16 | 1 day 1 minute | $ 36.00 |
17 | 3 days | $ 54.00 |
18 | 1 week | $ 126.00 |
In order to execute all these tests, he has to extend the durationMap
in the ParkCalcPage class with proper values (see Listing 2.26).
1 class ParkCalcPage
2
3 ...
4
5 @@durationMap = {
6 '30 minutes' => ['05/04/2010', '12:00', 'AM', '05/04/2010'
,'12:30', 'AM'],
7 '3 hours' => ['05/04/2010', '12:00', 'AM', '05/04/2010',
'03:00', 'AM'],
8 '5 hours' => ['05/04/2010', '12:00', 'AM', '05/04/2010',
'05:00', 'AM'],
9 '5 hours 1 minute' => ['05/04/2010', '12:00', 'AM',
'05/04/2010', '05:01', 'AM'],
10 '12 hours' => ['05/04/2010', '12:00', 'AM', '05/04/2010',
'12:00', 'PM'],
11 '24 hours' => ['05/04/2010', '12:00', 'AM', '05/05/2010',
'12:00', 'AM'],
12 '1 day 1 minute' => ['05/04/2010', '12:00', 'AM',
'05/05/2010', '12:01', 'AM'],
13 '3 days' => ['05/04/2010', '12:00', 'AM', '05/07/2010',
'12:00', 'AM'],
14 '1 week' => ['05/04/2010', '12:00', 'AM', '05/11/2010',
'12:00', 'AM']
15 }
16
17 ...
Tony executes all the tests and sees that they all pass. Alex seems to have implemented the functionality for the Valet Parking lot completely. The acceptance criteria on the back of the story card already provided Alex with the right answers for the implementation. As a final step, Tony checks in all the files he touched into the source code repository and indicates on the story for Valet Parking on the team’s taskboard that this story is automated and passing. Alex and Tony celebrate this success with a high-five at the end of the day.
This concludes the automation for the Valet Parking examples. We saw that Tony started out with Cucumber. He wrote down the first example in a natural language into a text file. He then started to automate this first example up to the point he felt comfortable with at his level of expertise. When Tony got stuck with the automation code, he paired up with Alex, the test automation programmer.
Alex and Tony implemented the ParkCalcPage driver, which fills in the parking lot and the starting and leaving dates and times into the web page form. After clicking the Calculate button, the parking costs are returned from a function, so that the test framework is able to check the actual value against the expected one.
When Alex and Tony started to pair, they could both contribute from their unique expertise. Tony, the tester, could critically think about the test code while Alex the automation programmer could help Tony get over the technical burden of test automation. By working together, they helped each other see things from a different perspective as well. Finally, the code that Alex and Tony produced is reviewed by definition. Having a second pair of eyes looking over the code while it is created is extremely valuable in team-based software development—and that includes team-based test automation as well.
By converting the first test into a scenario outline, Tony was able to automate the remaining examples for Valet Parking from the workshop as directly as possible. The business expert, Bill, should be able to find the examples Phyllis, Tony, and he discussed during the workshop in the output of the tests.
3.12.84.150