Testing with Frank

This is the first post I ever wrote, explaining how to implement testing with Frank, an open source tool for the automated testing of iPhone apps. Read the full article in the Wimdu tech blog or continue below.


A long, long time ago… we started to work on an iPhone app for the merchants of Wimdu (= our hosts). We want to provide them with a handy tool to manage all their bookings. We call it our “host app”.

While the project comes to the final stages of development we started to think about testing the application. It was clear from the very beginning, that we wanted to build automated tests to easily check every release for stability, quality and reliability.

We had a very specific set of requirements for our tests. We want our mobile app not to be a stand-alone or isolated platform, we will have interaction between the hosts (merchants) using the iPhone and our guests. Those interaction are mainly messages between the two parties as well as the actual bookings. If we want to simulate and test end-to-end user behaviour, we need to run simulations on our web- and mobile app simultaneous. The tests for the web app are written in Ruby, using Watir to hook into Selenium and drive Firefox. So we needed a system that could run the scripts for the web app simulation. Furthermore, we wanted to develop test driven, so we needed quite specific feature descriptions and scenarios for our test cases.

Naturally, the first step was to look into the UI Test Automation that is built into Xcode. Thinking, that as it’s fully integrated into Xcode and tthe perfect tool to work with, we tried to build some small test cases. The limitation of this tool is quite obvious: it can only test the iOS app. There would be a lot of manual steps involved to switch between the test cases of the iOS app and the web app. Hence, for this approach the build in tool box was not sufficient.

Amongst others we looked into Testing with Frank built by Thoughtworks. It was the best fit for our need. It uses the two languages we need, Cucumber to have a Product-Understandable side of the tests, where they can define and read test cases. On the other hand its build on Ruby. We love Ruby, so we tried it.

Frank is charming, what happens is pretty much explained in one picture:

frank

However, after running frank setup we stumbled upon the first problem as we were using Cocoapods for dependency management in our projects. You can fix it by passing the workspace and scheme to Frank like:

frank build --workspace Host.xcworkspace --scheme Host --configuration Debug

If you build Frank in release mode, then the “Frank Driver” will not access the “Frank Serve” any more. Other than that the build will succeed and we can follow Peter’s blog to write our first tests.

Getting started with the Cucumber tests we quickly realized that it would be a good idea to strictly divide the two “languages” Ruby and Cucumber. It creates a bit of an overhead for smallest step definitions, but as soon as things get a little more complicated it helps to organize the code.

In Example:

Cucumber reflects the specification from the Product Manager.
It lives in the default folder features/request.feature

Scenario:
as a host, who has a booking request I want to see the request in the app with the guest name displayed
  Given I have a booking request
  Then I should see 1 request
  And I should see the name of the guest

the step definitions are also in the default folder features/step_definitions/request_steps.rb. Here we decided to write as little Ruby as possible, to not have to places where the actual code is running. We do only require the according class:

require_relative "lib/requests"

requests = Requests.new

Given /^I have a booking request$/ do
  requests.pending_booking
  steps %{
    Given I log into the app with user #{requests.host}
    Given I touch "Requests"
  }
end

And /^I should see the name of the guest$/ do
  requests.find_guest_name
end

We tell the Ruby-Part of our tests to create a new booking request for a new host and gather information. We can also include other Cucumber steps if it fits the purpose of the scenario. The last step is now to write the actual test, it lives, as you can see already in features/step_definitions/lib/requests.rb:

class Requests
  include Frank::Cucumber::FrankHelper

  attr_accessor :host, :guest, :offer

  def initialize
    @app = Webapp.new
    self.host = @app.host
    self.offer = @app.create_offer(host)
    self.guest = @app.guest
  end

  def pending_booking
    booking = @app.create_booking(offer, guest, checkin_day, checkout_day)
  end

  def find_guest_name
    name_displayed = frankly_map("label marked:'Guests Name'", 'text')
    begin
      name_displayed.include?(guest.name).should == true
    rescue => e
      raise "Guest with #{guest.name} not found in the view"
    end
  end
end

All interaction with our web application is handled in the Webapp class. In this case a new host is created, a new offer for this host and a guest to book the offer. As we want to develop end-to-end user tests we do not create dummy entries in the API for the app but rather interact with both apps at once. The label we use in frankly_map is set in the Xcode project in the first place, so that the element is accessible for us in Frank.

How we work (with Watir) to write tests and simulations in our Webapp will be the next post to follow.