UI Tests – Default Data

I have been lucky over the past two years. I have had the opportunity to use Cucumber as a testing tool in several environments. I have worked on four web applications – one written in Groovy, one in PHP, and two in Java. I worked with a team developing a batch application. I worked with a team that was building Informatica transformations and I worked with a team developing an iPhone application. Even though each environment was quite different, cucumber worked as an amazing tool to implement Acceptance Test Driven Development in each case.

The most interesting thing about using Cucumber in such diverse environments is that I started to see similar patterns each time. In the first, second, and third post in this series we discussed the Page Object pattern. In this posting I will talk briefly about how this pattern applies to other environments. I will also talk about another pattern I discovered. I call it the Default Data pattern. Finally I will also talk about building high level Scenarios that express details only where necessary.

Page Object Pattern

The page object in its’ most basic form is an attempt to encapsulate access to an item we wish to test so we isolate ourselves from changes in that item. This easily translates to working with an iPhone. We created classes we called screen objects and I wrote a simple DSL similar to WatirHelper that I introduced in the last post. But how does this apply to applications without a user interface?

Applying Page Object to applications without a User Interface

One of the teams I worked with was developing Informatica transformations. The transformation process started by reading a fairly large amount of data from a collection of database tables. It then applied business logic to modify a significant portion of that data and ended by writing it into another collection of database tables. How is it possible to apply the page object pattern to this application?

At the lowest level our page object was implement as ActiveRecord objects. This provided a higher level abstraction over the individual database tables. But this application needed to read data from multiple tables. In order to solve this problem we created a higher level class that managed the creation of all of the ActiveRecord objects necessary for the input and another class that read the ActiveRecord objects that were the output. I will discuss how we handled the large amounts of data later in this post.

The batch application was handled in much the same. It had a very large flat file input and a very large flat file output. We built an object to represent the input and another to represent the output. These objects knew the expected format in the files but provided a high-level access to create and review the output that could be simply called from the step definitions.

Creating High-Level Scenarios

In the Scenarios we have created so far we are still coupling the steps to the thing we are testing. We are doing this by specifying every single keystroke and mouse click necessary to complete as example even when it is not necessary for the thing we are testing. Let’s see what can be done about this.

Let’s start by writing a higher-level scenario that does the same thing as the last one we created.


Scenario: Our first high level scenario
  When I purchase "Pragmatic Unit Testing (C#)"
  And I checkout with
  | name    | address         | email              | pay_type    |
  | Cheezy  | 123 Main Street | cheezy@example.com | Credit card |
  Then I should see "Thank you for your order"

This is a good start. We’ll need to write one step definition and add a method to our page object. Here’s the step:

  
And /^I checkout with$/ do |table|
  @checkout_page = @shopping_cart.goto_checkout_page
  @checkout_page.complete_order(table.hashes.first)
end

and here is the new method on CheckoutPage:

  
def complete_order(data)
  self.name = data['name']
  self.address = data['address']
  self.email = data['email']
  self.pay_type = data['pay_type']
  place_order
end

This change starts to take us further away from the tight coupling we are trying to avoid. But as I continued my quest to write cleaner and more precise tests I made another very important discovery. That discovery was that the majority of the data I was providing for each Scenario had absolutely nothing to do with the specific thing I was trying to test. For example, in the Scenario above we are trying to test if we get the expected message when we complete an order. The data we enter on the CheckoutPage is completely irrelevant. As long as we provide some valid data we will get the same results. I found this pattern to be largely true in nearly all of the applications I tested.

Default Data

What I discovered was that for the vast majority of Scenarios the actual data requirements that were specific to the thing we were testing was very small. And yet sometimes the amount of data needed to complete the scenario was fairly high. What I needed was a way to have the system provide default data for everything that was not specific to the scenario and yet allow me to override any default data that had to be exact for the test.

Let’s take a look at how we might do this with our depot application. I’m going to add two new scenarios.


Scenario: Using some default data
  When I purchase a book
  And I checkout with
  | pay_type    |
  | Credit card |
  Then I should see "Thank you for your order"

Scenario: Using all default data
  When I purchase a book
  And I complete the order
  Then I should see "Thank you for your order"

We will have to add two new step definitions.

  
When /^I purchase a book$/ do
  @shopping_cart = @catalog.add_book_to_shopping_cart('Pragmatic Version Control')
end

And /^I complete the order$/ do
  @checkout_page = @shopping_cart.goto_checkout_page
  @checkout_page.complete_order
end

And finally we’ll have to make a couple of changes to our CheckoutPage. Here is the class in its’ entirety:

  
class CheckoutPage
  include WatirHelper

  DEFAULT_DATA = {
    "name" => "Cheezy",
    "address" => "123 Main Street",
    "email" => "cheezy@example.com",
    "pay_type" => "Check"
  }

  text_field(:name, :id => "order_name")
  text_field(:address, :id => "order_address")
  text_field(:email, :id => "order_email")
  select_list(:pay_type, :id => "order_pay_type")
  button(:place_order, :value => "Place Order")

  def initialize(browser)
    @browser = browser
  end

  def complete_order(data = {})
    data = DEFAULT_DATA.merge(data)
    self.name = data['name']
    self.address = data['address']
    self.email = data['email']
    self.pay_type = data['pay_type']
    place_order
  end
end

Starting on line 4 we create a hash that contains all of our default data. This data is used in the complete_order method that begins on line 21. You will note that we have provided an empty hash as the parameter for this method so it can be called without data and accept all default data. On line 22 we merge the data passed in with the default data. This simple pattern gives us the ultimate flexibility. We have a complete set of default data and yet we have the ability to override any part of it based on what we are testing.

I found out that this pattern also works with other types of testing. In the Informatica project I mentioned earlier in the post we used factory_girl to provide default data for all of the tables and had our wrapper object override the specific data elements that were absolutely necessary for each Scenario. For the batch application the input and output objects had the ability to provide default data and allowed the Scenarios to override what was necessary. Using this pattern we were able to create much higher level tests and eliminate much of the verbosity we would have had otherwise.

In Closing

I have found out that creating higher level tests makes my cucumber scenarios more robust and flexible. Tests that are incredibly verbose are much harder to read and are a lot harder to maintain over time.

We still have a ways to go in this series. You can download the finished code from this posting here or you can clone it from github. Please leave a reply and let me know what you think about these ideas.

12 thoughts on “UI Tests – Default Data

  1. Pingback: Tweets that mention UI Tests – Default Data | CheezyWorld -- Topsy.com

  2. Pingback: UI Tests – putting it all together | CheezyWorld

  3. Pingback: My simple Cucumber + Watir page object pattern framework | WatirMelon

  4. Pingback: Alister Scott: A simple Cucumber + Watir page object pattern framework « Watir

  5. Thanks a lot! I am noob in programming and this is my first steps on the way of UI testing so I am very grateful for such a useful series. Only disappointment that my experience not enough to understand all of syntactic constructions.

    • Sergey,

      Don’t give up. Once you understand the language a little better you will find this approach very easy and quite enjoyable.

  6. We have a requirement to work on cucumber.you have explained minute details well.
    Your site is very much useful for both starters and advanced users.

    • Reddy, thanks for the complement and please stay tuned. In a couple of weeks I’m going to provide updates for my book and introduce my new gem with a few detailed posts. I hope you enjoy those as well.

  7. This is a really nice article. But I’m curious how you handle more complex data conditions. One of the things I’m finding is that using Cucumber for UI testing (as opposed to integration-level testing) is tricky if you have complex applications with nested data conditions. For example, let’s say you have a situation where your full data condition is:

    A private equity holding … that is attached to a fund … that is part of an account … that is associated with a product.

    Further, each one of those things may need to have certain configurations. So it’s not just “a fund” it’s a fund that is specifically offshore and market-driven. It’s not just “an account” but an account that has an owner set up. And it’s not just “a product”, but rather a product with General Ledger enabled.

    How do you specify that all in a Given? What happens when later you need something almost similar to what I described above but instead of offshore, the fund is onshore (otherwise everything else is the same)?

    From an integration perspective, we’d have a Factory create the objects for us. In the UI sense, we have to either check if those things exist in the system and enter them if they do not. (Or just assume they do as part of standard — default — data.) But, again, how do you go about specifying something like this with Cucumber in a way that’s readable?

    Have you had experience with complex data conditions like this?

    • Jeff,

      I can’t say I have had the exact same experience you are describing but I have worked with several teams where there were very large data requirements. On one project we had to setup data in over 30 tables, run a large batch-like process, and then verify data in over 20 different tables. Believe it or not, we used a variation on the default data pattern presented above.

      How we solved this problem is we created ActiveRecord classes that mapped to each table and a set of factory-girl factories for each table that provided the default data for the input. We then created a higher level factory class that utilized the factory-girl factories to create a named snapshot that populated all of the tables. I was able to say things like:

      Given I had an “Auto” policy with “2″ vehicles and “2″ listed drivers

      We also provided the ability to override any single column or set of columns in a table very simply. In the end we created something that was flexible enough for our needs.

      On the output we created a set of ActiveRecord classes that mapped to the output tables and a high level verification class that could simply look in any of the tables and verify the data in columns.

      As I recall, it took us four days to put this framework in place but once it was there we were able to write features rather quickly.

  8. Pingback: Telling real stories in Acceptance Test Driven Development « Stephan Schwab

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>