Chapter 2. Valet Parking Automation

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.

Image

Figure 2.1. Test automation architecture that supports ATDD

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.

The First Example

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).

Listing 2.1. Tony’s first Valet Parking test

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.

Listing 2.2. Shell output from first Valet Parking test

 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.

Listing 2.3. Initial step definitions for first test

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

Listing 2.4. Shell output from first Valet Parking test with step definitions in place

 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.

Image

Figure 2.2. The folder layout after Tony put everything in place

Listing 2.5. Support code for setting up the Selenium client

 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.

Listing 2.6. Initial ParkCalcPage class

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).

Listing 2.7. Initial wishful implementation of the first keyword

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).

Listing 2.8. Empty implementations added to the ParkCalcPage class

 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.

Listing 2.9. Initial wishful implementation of the first keyword

1 Then /^I will have to pay (.*)$/ do |price|
2   $parkcalc.parking_costs.should == price
3   pending
4 end

Listing 2.10. Empty implementation of the parking cost calculation step

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.

Pairing for the First Test

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).

Image

Figure 2.3. Alex’s mockup for the airline parking lot calculator

Initializers

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.

Listing 2.11. Selecting the right parking lot entry from the dropdowns

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?

Listing 2.12. The map that holds durations and maps to actual dates and times

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).

Listing 2.13. The six parameters for the form are derived from the durationMap

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.

Listing 2.14. The starting date and time is filled into the proper form fields

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.

TONY

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).

Listing 2.15. The leaving date and time are added to the previous function.

 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.

Listing 2.16. Extracted method for filling in parking durations

 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”?

ALEX

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.

Listing 2.17. The final version of the ParkCalcPage for the initialization steps

 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

Checking the Results

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.

Listing 2.18. The initial version of the check

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).

Listing 2.19. Shell output from first Valet Parking test

 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

TONY

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).

Listing 2.20. The check after factoring out two functions for the individual steps

 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.

Listing 2.21. The final version of the Valet Parking steps for the first test

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

Listing 2.22. The final version of the ParkCalcPage class for the first test

 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

Tabulated Tests

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.

Listing 2.23. The first test converted to a tabulated format

 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.

Listing 2.24. Shell output with the first test in a tabulated format

 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).

Listing 2.25. All examples from the workshop filled into the table

 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).

Listing 2.26. The ParkCalcPage class with the extended durationMap for all Valet Parking tests

 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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.84.150