SlideShare a Scribd company logo
1 of 62
Download to read offline
meets iPhone




                    OSCON 2009
                              Ian Dees
                      http://www.ian.dees.name

Hi, everyone. Welcome to OSCON. This talk is about testing the GUIs of
iPhone apps using the Ruby language and the Cucumber test framework.
#osconcuke




First, a bit of business. Per Giles Bowkett, hereā€™s a Twitter hashtag for
this talk. Throughout the presentation, audience members can tweet
their questions here. This will hopefully keep people from being put on
the spot, and will also provide a place to ļ¬ll in any answers we donā€™t
have time for today.
p
Now, a bit about what I doā€”because you're no doubt deeply caring and
curious people, and because it will shed light on what we're doing here
today. I write software for Tektronix, makers of test equipment (though
this talk isn't about my day job). The interfaces we build involve things
like touch screens and physical knobs. Each style of interface demands
new ways of testing.
Iā€™ve written a book that tells that kind of storyā€”adapting tests to the
quirks and realities of a platformā€”in code.
?
When a new platform comes out, be it a phone or a runtime or an OS, I
get curious: how do they test that? And thatā€™s what led me to think
about the kinds of things Iā€™m talking about today. So, on to our topic:
testing the iPhone with Cucumber.
CUCUMBER
                 http://cukes.info




So, Cucumber.
LETā€™S GET U INTO
             BEHAVIOUR-DRIVEN
               DEVELOPMENT



Cucumber is a Behavio(u)r-Driven Development framework built on
Ruby. You write your top-level tests in a language suited for that task
(English). And you write your glue code in a language suited for that task
(Ruby).
FLAVOUR OF THE MONTH?




Flavor of the month? (Buzzword) bingo! BDD is indeed a ļ¬‚avor, a
seasoning, a heuristic which you use insofar as it helps you make sense
of your projects.
EXECUTABLE EXAMPLES




For the purposes of this talk, think of BDD as an appreciation of
executable examples. Early conversations about software projects tend
to take the form of examples and use cases; why not express them as
running code and have some smoke tests you can use later?
Ya gotta have a real project to throw your ideas against.
                     ā€”Gus Tuberville (heavily paraphrased)




Itā€™s all well and good to add a couple of token tests to a ā€œHello, worldā€
project. But itā€™s much more instructive to actually try to solve some real
problem.
QUESTION


    ā€œOh crap, OSCON accepted my talk submission.
                 Now what do I do?ā€




So I looked for a real-world problem to solve.
QUESTION


      ā€œCan BDD be used to write an iPhone app?ā€




Something that wasnā€™t related speciļ¬cally to this conference, or even to
the general task of seeing how well BDD and iPhone development ļ¬t
together.
QUESTION


                     ā€œWhatā€™s on the radio?ā€




It had to be something I would have liked to do anyway, irrespective of
conferences and technologies. Something to keep the imagination
circuits grounded in reality.
JUST
         PLAYED
     http://www.25y26z.com




So I undertook to write an iPhone app that would let me ā€œbookmarkā€
what radio station is playing any given moment and look the information
up later. Yes, Iā€™ve heard of pencil and paper, Shazam, and the Zune.
There are tradeoffs that make this app better than those approaches in
some ways, and worse in others.
HOW THIN IS YOUR GUI?
               Presenter First Techniqueā€”Brian Marick
                http://exampler.com/src/mwrc-start.zip




Do you need a full-stack GUI test? If your GUI is an ultra-thin, ultra-
reliable layer on top of an API, many of your automated tests can bypass
the GUI and talk directly to the API.
DRIVING A GUI



 1.Push the button
 2.See what happens



At some point, though, itā€™s nice to see the real thing running, top to
bottom. And thatā€™s the approach I took with the Just Played app.

The two main phases of automating a GUI are simulating events and
inspecting controls.
DRIVING THE PHONE
                            Matt Gallagher
                  http://cocoawithlove.com/2008/11/
               automated-user-interface-testing-on.html




Matt Gallagher has done the groundbreaking work on both these fronts.
In a series of articles on his Cocoa With Love blog, he discusses how to
synthesize touch screen events and interrogate the GUI on an iPhone.
touchesBegan:withEvent:
                                    touchesMoved:withEvent:
                                    touchesEnded:withEvent:




Hereā€™s how Mattā€™s technique works. When you tap a button on an
iPhone, the Cocoa Touch runtime looks at the buttonā€™s implementation
for these three methods and calls them in sequence...
touchesBegan:withEvent:
      touchesMoved:withEvent:
      touchesEnded:withEvent:




...
touchesBegan:withEvent:
      touchesMoved:withEvent:
      touchesEnded:withEvent:




...
touchesBegan:withEvent:
      touchesMoved:withEvent:
      touchesEnded:withEvent:




...
WISH WE WERE HERE

UITouch *touch   = [[[UITouch alloc] init] autorelease];
NSSet   *touches = [NSSet setWithObject:touch];
UIEvent *event   = [[[UIEvent alloc] init] autorelease];

[view touchesBegan:touches withEvent:event];
[view touchesEnded:touches withEvent:event];




So all we have to do is create the UITouch and UIEvent objects that the
functions expect to see, and all will be well, right? Except that Apple
didnā€™t provide initializers for these objects. And they have private ļ¬elds
that have to be set up just so, or your app will crash.
CATEGORIES
   @interface UITouch (Synthesize)

   -   (id)initInView:(UIView *)view;
   -   (void)setPhase:(UITouchPhase)phase;
   -   (void)setLocationInWindow:(CGPoint)location;
   -   (void)moveLocationInWindow;

   @end




In Ruby, youā€™d write a monkey patch, modifying the vendorā€™s class at
runtime. Objective-C offers a slightly more civilized take on the concept,
called a category. You can add methods to a third-party class, and spell
out the scope in which they apply. Your new methods have access to the
classā€™s private ļ¬elds; lucky for would-be GUI testers!
- (id)initInView:(UIView *)view
{
    // ... code omitted for brevity ...
    _tapCount = 1;
    _locationInWindow =
        CGPointMake(
            frameInWindow.origin.x + 0.5 * frameInWindow.size.width,
            frameInWindow.origin.y + 0.5 * frameInWindow.size.height);
    _previousLocationInWindow = _locationInWindow;

    UIView *target =
        [view.window hitTest:_locationInWindow withEvent:nil];

    _window = [view.window retain];
    _view = [target retain];
    _phase = UITouchPhaseBegan;
    _touchFlags._firstTouchForView = 1;
    _touchFlags._isTap = 1;
    _timestamp = [NSDate timeIntervalSinceReferenceDate];
    // ... code omitted for brevity ...
}


I gather this wasnā€™t easy. It probably involved making an educated
guess, reading the crash logs, trying to read Appleā€™s minds, making
some tweaks, and watching it crash all over again. Here are just some of
the private ļ¬elds whose purposes, or at least required values, Matt was
able to divine.
DESCRIPTIONS
 @interface UIView (XMLDescription)

 - (NSString *) xmlDescriptionWithStringPadding:(NSString *)padding;

 @end

 // ---- snippet from implementation:

 if ([self respondsToSelector:@selector(text)])
 {
     [resultingXML appendFormat:@"n%@t<text><![CDATA[%@]]></text>",
      padding, [self performSelector:@selector(text)]];
 }




The same techniqueā€”patching existing classes at runtimeā€”works for
implementing the other half of the GUI testing equation. Hereā€™s a
glimpse at how categories put a uniform face on runtime descriptions:
location, type, caption, and other information for every control on the
screen.
SCRIPT RUNNER




But how do you specify which button gets clicked, and when, and what
the expected outcome is? The original approach provides a ScriptRunner
object that reads in a list of commands from a text ļ¬le.
BROMINE
                         (not the openqa one)
                             Felipe Barreto
                    http://bromine.googlecode.com




Mattā€™s original proof-of-concept embedded the test script inside a
special build of an app, which ran ā€œheadlessā€ā€”working tests, but no GUI.
Felipe Barreto packaged up the code for running with a visible GUI, in
the simulator or on the iPhone. The project is called Bromine (as a nod
to the Selenium web testing tool), and is drop-in ready.
<plist version="1.0">
     <array>
         <dict>
             <key>command</key>
             <string>simulateTouch</string>
             <key>viewXPath</key>
             <string>//UIToolbarButton[1]</string>
         </dict>
     </array>
     </plist>




The test script format for Bromine, like Mattā€™s project before it, is an
XML-based property ļ¬le. Itā€™s simple to parse from Cocoa Touch, but it
requires re-bundling your app every time you change your test script.
Also, adding conditional tests requires custom Objective-C code.
curl --url http://localhost:50000 -d                   
     "<plist version="1.0">                               
      <dict>                                                
          <key>command</key>                                
          <string>simulateTouch</string>                    
          <key>viewXPath</key>                              
          <string>//UIToolbarButton[1]</string>             
      </dict>                                               
      </plist>"




So just for fun, I found an open-source Objective-C HTTP server,
dropped it into the project, and taught Bromine how to read its
commands from the server instead of a ļ¬le. Now you can keep your app
running and drive it from curl.
require 'tagz'

     command = Tagz.tagz do
       plist_(:version => 1.0) do
         dict_ do
           key_    'command'
           string_ 'simulateTouch'
           key_    'viewXPath'
           string_ '//UIToolbarButton[1]</string>'
         end
       end
     end

     Net::HTTP.post_quick 
       'http://localhost:50000/', command


Or Ruby.
BROMINE + NET = BROMINET
                  http://github.com/undees/brominet




 I lightheartedly call the result ā€œBrominet,ā€ as a nod to the new-found
network connectivity (and because itā€™s missing a bunch of functions the
full Bromine library has).
COMPATIBILITY

                        2.2        3.0       3.1     3.2 beta

          Simulator      āœ“         āœ“          āœ“          āœ“

                                                       hope
            iPhone       āœ“         āœ“          āœ“
                                                        so




One of the drawbacks of using categories to set private ļ¬elds is that the
vendor might change those ļ¬elds out from under you. Fortunately, the
Bromine project has volunteers who ļ¬gure out how to stay on top of OS
changes. Iā€™ve been porting their updates over to Brominet.
Weā€™ve now got enough pieces to look at some of the test scripts for the
Just Played app, top to bottom.
story

                       step deļ¬nition

                          ad-hoc API

                          GUI driver


The top-level user stories are in Cucumberā€™s test-oriented subset of
English. The Ruby code to press speciļ¬c buttons is effectively an ad-hoc,
informally speciļ¬ed, bug-ridden API for the app. A layer of glue codeā€”
step deļ¬nitionsā€”sits between them. Down in the plumbing, we make
angle brackets and post them via HTTP.
Feature: Stations
   In order to keep my day interesting
   As a radio listener with varied tastes
   I want to track songs from many stations

    Scenario: Deleting stations
      Given a list of radio stations "KNRK,KOPB"
      When I delete the first station
      Then I should see the stations "KOPB"




                                                                          s
Hereā€™s a story from late in the appā€™s development. As with all the
features, I wrote the story ļ¬rst, and the feature later. The ļ¬rst few lines
are just commentary, imploring the test to justify its own existence.
Given a list of radio stations "KNRK,KOPB"




   When I delete the first station




   Then I should see the stations "KOPB"




                                                             s
The real heart of the tests are the Given/When/Then lines.
Given /^a list of radio stations "(.*)"$/




   When /^I delete the first station$/




   Then /^I should see the stations "(.*)"$/




                                                                 d
Cucumber searches among the Ruby step deļ¬nitions youā€™ve provided to
ļ¬nd the match for each step.
Given /^a list of radio stations "(.*)"$/ do




   end

   When /^I delete the first station$/ do

   end

   Then /^I should see the stations "(.*)"$/ do




   end
                                                                       d
When Cucumber hits a line of your test script, it runs the block of code
attached to the matching step deļ¬nition. We could put HTTP POST
requests directly in these blocks.
Given /^a list of radio stations "(.*)"$/ do
     |stations|

     app.stations = stations.split(',')
   end

   When /^I delete the first station$/ do
     app.delete_station(0)
   end

   Then /^I should see the stations "(.*)"$/ do
     |text|

     stations = text.split ','
     app.stations.should == stations
   end
                                                                         d
But itā€™s often useful to wrap the speciļ¬c button presses up in an
abstraction. Rather than say, ā€œpress the button whose caption is
ā€˜Delete,ā€™ā€ you say, ā€œdelete something,ā€ and rely on your ad-hoc API to
deļ¬ne how deleting happens in the GUI.
class JustPlayed
       def stations=(list)
         plist = Tagz.tagz do
           array_ do
             list.each {|station| string_ station}
           end
         end

         @gui.command 'setTestData', :raw,
           'stations', plist
         end
       end
     end

                                                                      a
Here are a few pieces of that API. First, for the Given step, weā€™re
bypassing the GUI and pre-loading the app with canned data. (In the
context of Rails apps, David Chelimsky calls this technique Direct Model
Access.) Brominet sees the ā€œsetTestDataā€ command, recognizes it as
something not GUI-related, and hands it off to the appā€™s delegate class.
class JustPlayed
   def delete_station(row)
     @gui.command 'scrollToRow',
       'viewXPath', '//UITableView',
       'rowIndex', row

       @gui.command 'simulateSwipe',
         'viewXPath', "//UITableViewCell[#{row + 1}]"

       @gui.command 'simulateTouch',
         'viewXPath', '//UIRemoveControlTextButton',
         'hitTest', 0

     sleep 1
   end
 end
                                                                       a
Once the GUI has the canned test data, we can push some buttons.
Three things to note: 1) Bromine can scroll controls into view so they
can be tapped; 2) since Bromine sees the view hierarchy as one big XML
tree, we specify controls using XPath; and 3) simulateSwipe is deprecated
ā€”a new version of this slideā€™s code is posted on GitHub.
class JustPlayed
  def stations
    xml = @gui.dump
    doc = REXML::Document.new xml

    xpath = '//UITableViewCell[tag="%s"]' % StationTag
    titles = REXML::XPath.match doc, xpath
    titles.map {|e| e.elements['text'].text}
  end
end



                                                                          a
To get the result, we just serialize the entire GUI and grub through it
from the comfort of the desktop. This process is slow, but allows
Brominet to be built on a small set of primitives.
module Encumber
             class GUI
               def command(name, *params)
                 command = Tagz.tagz do
                   plist_(:version => 1.0) do
                     dict_ do
                       key_ 'command'
                       string_ name
                       params.each_cons(2) do |k, v|
                         key_ k
                         string_ v
                       end
                     end
                   end
                 end

                 Net::HTTP.post_quick 
                   "http://#{@host}:#{@port}/", command
               end
             end
           end                                                         g
All those library functions call into a common class whose responsibility
is posting HTTP requests. It uses the Tagz library to build XML requests
and Net::HTTP to send them. This part of the code should be reusable in
any Brominet project.
FAKING THE TIME




The canned-data approach is about to rear its head again, as weā€™re
going to fake out parts of the physical world.
Feature: Snaps
        In order to find out what song is playing
        As a radio listener without a mic or data
        I want to remember songs for later lookup

         Scenario: Snapping a song
           Given a list of radio stations "KNRK"
           And a current time of 23:45
           Then the list of snaps should be empty
           When I press KNRK
           Then I should see the following snaps:
             | station | time     |
             | KNRK    | 11:45 PM |

                                                                      s
The ļ¬rst thing I committed to the repository, even before creating a new
Xcode project, was an older version of this story.

See the line about the current time of 23:45?
Given /^a current time of (.*)$/ do
              |time|

              app.time = time
            end




                                                                          d
Without having control over the time, weā€™d have to resort to creating a
bookmark and then checking the time right after, with some sort of
fuzzy matching.
class JustPlayed
              def time=(time)
                @gui.command 'setTestData',
                  'testTime', time
              end
            end




                                                                       a
The app has to support this testing hook internally and worry about
whether itā€™s supposed to use the real time or the fake one. This juggling
introduces complexity. But youā€™re seeing the worst case. The preloading
of radio stations you saw earlier met with a much happier fate: the
support code was useful for implementing save/restore.
TABLES




One of Cucumberā€™s ballyhooed features is its support for tabular data.
Feature: Lookup
   In order to find out what songs I heard earlier
   As a radio listener with network access
   I want to look up the songs I bookmarked

 Scenario: Partial success
     Given a list of radio stations "KNRK"
     And a test server
     And the following snaps:
       | station | time     |
       | KNRK    | 2:00 PM |
       | KNRK    | 12:00 PM |
     When I look up my snaps
     Then I should see the following snaps:
       | title                | subtitle         |
       | KNRK                 | 2:00 PM          |
       | Been Caught Stealing | Jane's Addiction |
                                                       s
You can draw ASCII-art tables right in your stories.
Given /^the following snaps:$/ do
    |snaps_table|

    snaps_table.hashes
      #=> [{'station' => 'knrk', 'time' => '2:00 PM'} ...]

    # do fancier things, like manipulating columns
    app.snaps = snaps_from_table(snaps_table, :with_timestamp)
  end




                                                                      d
If a test step comes with its own table, Cucumber passes it into the
deļ¬nition block as an object you can poke at. Most of the time, youā€™ll
just want to grab its ā€œhashesā€ array, which gives you one hash per row.
But you can also do more complicated table processing, like renaming
columns.
TAGS




One more slice of Cucumber I found convenient for this project is the
notion of tagging tests as belonging to different categories.
Feature: Stations
      ...

       @restart
       Scenario: Remembering stations
         Given a list of radio stations "KBOO,KINK"
         And a test server
         When I restart the app
         Then I should see the stations "KBOO,KINK"




                                                                         s
For instance... Appleā€™s Instruments tool doesnā€™t like it when your app
exits. So I tagged all the scenarios that required a restart.
cucumber -t~@restart features




When you run your tests, you can tell Cucumber to include or exclude
particular tags.
RESULTS




For an investment of about 10% of the projectā€™s time maintaining the
test infrastructure, using executable examples served to: 1) drive out the
design, 2) catch a couple of minor bugs, 3) help replicate other bugs
found manually, and 4) ļ¬nd leaks (mine and othersā€™). Plus, now I know
whatā€™s on the radio.
}         /undees/justplayed




The app is available for free on the App Store. The source code is up on
the ā€˜Hub and in the ā€˜Bucket.
CONVERSATION

 ā€¢ Barnes and Noble booth
   During lunch today

 ā€¢ Meet Authors   from the Pragmatic Bookshelf
   7 PM tonight, Ballroom A7

 ā€¢ undees@gmail.com

 ā€¢ http://twitter.com/undees



If youā€™d like to continue the conversation, catch me in the hall, or come
to one of the scheduled events.
SHOULDERS OF GIANTS

 ā€¢ Bromine                             ā€¢ Pivotal Tracker

 ā€¢ cocoahttpserver                     ā€¢ iBetaTest

 ā€¢ ASIHTTPRequest                      ā€¢ api.yes.com

 ā€¢ hg-git




Special thanks to yes.com for making their radio playlist API available
under a Creative Commons license.
CREDITS

 ā€¢ Ruby   icon (c) Iconshock

 ā€¢ Instrument    photos (c) Tektronix

 ā€¢ Squirrel   (cc) kthypryn on Flickr

 ā€¢ Hand    (cc) myklroventine on Flickr

 ā€¢ Logos   (tm) github and bitbucket



Writing the software and preparing the material for this talk has been a
blast. I canā€™t wait to see what the rest of OSCON has in store for us, and
am grateful for our time together this week. Cheers.
BONUS SLIDES




Here are a few more tidbits that didnā€™t make it into the main
presentation.
UNIT TESTING
                  RSpec and iPhoneā€”Dr. Nic Williams
                  http://drnicwilliams.com/2008/07/04/
           unit-testing-iphone-apps-with-ruby-rbiphonetest/




I experimented a little with using Dr. Nicā€™s rbiphone library to unit-test
my Objective-C model classes from Ruby using the RSpec framework.
This technique wasnā€™t a perfect ļ¬t for the models in my app, which were
so tied to Apple APIs that there wasnā€™t much for the tests to do.
SCREEN CAPTURE
                       Google Toolbox for Mac
        http://code.google.com/p/google-toolbox-for-mac/wiki/
                          iPhoneUnitTesting




Google has a suite of Mac and iPhone development tools. Their iPhone
unit test code is geared toward capturing screen images and comparing
them against a reference. Heavily graphical apps, or apps that donā€™t use
the standard UIKit controls, spring to mind for a technique like this.

More Related Content

Similar to Cucumber meets iPhone

Demystifying dot NET reverse engineering - Part1
Demystifying  dot NET reverse engineering - Part1Demystifying  dot NET reverse engineering - Part1
Demystifying dot NET reverse engineering - Part1
Soufiane Tahiri
Ā 
Drupal 7 ci and testing
Drupal 7 ci and testingDrupal 7 ci and testing
Drupal 7 ci and testing
Claudio Beatrice
Ā 

Similar to Cucumber meets iPhone (20)

Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)
Ā 
Lecture #3 activities and intents
Lecture #3  activities and intentsLecture #3  activities and intents
Lecture #3 activities and intents
Ā 
Automated UI Testing for Web and Native Apps on iOS and Android
Automated UI Testing for  Web and Native Apps on iOS and AndroidAutomated UI Testing for  Web and Native Apps on iOS and Android
Automated UI Testing for Web and Native Apps on iOS and Android
Ā 
Android design patterns
Android design patternsAndroid design patterns
Android design patterns
Ā 
Mobile optimization
Mobile optimizationMobile optimization
Mobile optimization
Ā 
UI Testing for Your Xamarin.Forms Apps
UI Testing for Your Xamarin.Forms AppsUI Testing for Your Xamarin.Forms Apps
UI Testing for Your Xamarin.Forms Apps
Ā 
Google App Inventor
Google App InventorGoogle App Inventor
Google App Inventor
Ā 
[Srijan Wednesday Webinar] Mastering Mobile Test Automation with Appium
[Srijan Wednesday Webinar] Mastering Mobile Test Automation with Appium[Srijan Wednesday Webinar] Mastering Mobile Test Automation with Appium
[Srijan Wednesday Webinar] Mastering Mobile Test Automation with Appium
Ā 
Ruby motion勉強会 2012幓7꜈
Ruby motion勉強会 2012幓7꜈Ruby motion勉強会 2012幓7꜈
Ruby motion勉強会 2012幓7꜈
Ā 
Xam expertday
Xam expertdayXam expertday
Xam expertday
Ā 
Demystifying dot NET reverse engineering - Part1
Demystifying  dot NET reverse engineering - Part1Demystifying  dot NET reverse engineering - Part1
Demystifying dot NET reverse engineering - Part1
Ā 
Drupal 7 ci and testing
Drupal 7 ci and testingDrupal 7 ci and testing
Drupal 7 ci and testing
Ā 
Introduction to GUIs with guizero
Introduction to GUIs with guizeroIntroduction to GUIs with guizero
Introduction to GUIs with guizero
Ā 
RobotStudiopp.ppt
RobotStudiopp.pptRobotStudiopp.ppt
RobotStudiopp.ppt
Ā 
ie450RobotStudio.ppt
ie450RobotStudio.pptie450RobotStudio.ppt
ie450RobotStudio.ppt
Ā 
iLabs Toolbox Javashare 2008
iLabs Toolbox Javashare 2008iLabs Toolbox Javashare 2008
iLabs Toolbox Javashare 2008
Ā 
Android - Open Source Bridge 2011
Android - Open Source Bridge 2011Android - Open Source Bridge 2011
Android - Open Source Bridge 2011
Ā 
State ofappdevelopment
State ofappdevelopmentState ofappdevelopment
State ofappdevelopment
Ā 
DDive11 - Mobile Development For Domino
DDive11 - Mobile Development For DominoDDive11 - Mobile Development For Domino
DDive11 - Mobile Development For Domino
Ā 
Techniques For A Modern Web UI (With Notes)
Techniques For A Modern Web UI (With Notes)Techniques For A Modern Web UI (With Notes)
Techniques For A Modern Web UI (With Notes)
Ā 

More from Erin Dees

Write Your Own JVM Compiler
Write Your Own JVM CompilerWrite Your Own JVM Compiler
Write Your Own JVM Compiler
Erin Dees
Ā 

More from Erin Dees (10)

Your Own Metric System
Your Own Metric SystemYour Own Metric System
Your Own Metric System
Ā 
Thnad's Revenge
Thnad's RevengeThnad's Revenge
Thnad's Revenge
Ā 
Logic Lessons That Last Generations
Logic Lessons That Last GenerationsLogic Lessons That Last Generations
Logic Lessons That Last Generations
Ā 
JRuby, Not Just For Hard-Headed Pragmatists Anymore
JRuby, Not Just For Hard-Headed Pragmatists AnymoreJRuby, Not Just For Hard-Headed Pragmatists Anymore
JRuby, Not Just For Hard-Headed Pragmatists Anymore
Ā 
Playfulness at Work
Playfulness at WorkPlayfulness at Work
Playfulness at Work
Ā 
Write Your Own JVM Compiler
Write Your Own JVM CompilerWrite Your Own JVM Compiler
Write Your Own JVM Compiler
Ā 
How 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 book
How 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 bookHow 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 book
How 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 book
Ā 
How 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 book
How 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 bookHow 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 book
How 5 people with 4 day jobs in 3 time zones enjoyed 2 years writing 1 book
Ā 
A jar-nORM-ous Task
A jar-nORM-ous TaskA jar-nORM-ous Task
A jar-nORM-ous Task
Ā 
Yes, But
Yes, ButYes, But
Yes, But
Ā 

Recently uploaded

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
Ā 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Enterprise Knowledge
Ā 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
Ā 

Recently uploaded (20)

Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
Ā 
šŸ¬ The future of MySQL is Postgres šŸ˜
šŸ¬  The future of MySQL is Postgres   šŸ˜šŸ¬  The future of MySQL is Postgres   šŸ˜
šŸ¬ The future of MySQL is Postgres šŸ˜
Ā 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
Ā 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Ā 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
Ā 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
Ā 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Ā 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
Ā 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Ā 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
Ā 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
Ā 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
Ā 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
Ā 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
Ā 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
Ā 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
Ā 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
Ā 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
Ā 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
Ā 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Ā 

Cucumber meets iPhone

  • 1. meets iPhone OSCON 2009 Ian Dees http://www.ian.dees.name Hi, everyone. Welcome to OSCON. This talk is about testing the GUIs of iPhone apps using the Ruby language and the Cucumber test framework.
  • 2. #osconcuke First, a bit of business. Per Giles Bowkett, hereā€™s a Twitter hashtag for this talk. Throughout the presentation, audience members can tweet their questions here. This will hopefully keep people from being put on the spot, and will also provide a place to ļ¬ll in any answers we donā€™t have time for today.
  • 3. p Now, a bit about what I doā€”because you're no doubt deeply caring and curious people, and because it will shed light on what we're doing here today. I write software for Tektronix, makers of test equipment (though this talk isn't about my day job). The interfaces we build involve things like touch screens and physical knobs. Each style of interface demands new ways of testing.
  • 4. Iā€™ve written a book that tells that kind of storyā€”adapting tests to the quirks and realities of a platformā€”in code.
  • 5. ? When a new platform comes out, be it a phone or a runtime or an OS, I get curious: how do they test that? And thatā€™s what led me to think about the kinds of things Iā€™m talking about today. So, on to our topic: testing the iPhone with Cucumber.
  • 6. CUCUMBER http://cukes.info So, Cucumber.
  • 7. LETā€™S GET U INTO BEHAVIOUR-DRIVEN DEVELOPMENT Cucumber is a Behavio(u)r-Driven Development framework built on Ruby. You write your top-level tests in a language suited for that task (English). And you write your glue code in a language suited for that task (Ruby).
  • 8. FLAVOUR OF THE MONTH? Flavor of the month? (Buzzword) bingo! BDD is indeed a ļ¬‚avor, a seasoning, a heuristic which you use insofar as it helps you make sense of your projects.
  • 9. EXECUTABLE EXAMPLES For the purposes of this talk, think of BDD as an appreciation of executable examples. Early conversations about software projects tend to take the form of examples and use cases; why not express them as running code and have some smoke tests you can use later?
  • 10. Ya gotta have a real project to throw your ideas against. ā€”Gus Tuberville (heavily paraphrased) Itā€™s all well and good to add a couple of token tests to a ā€œHello, worldā€ project. But itā€™s much more instructive to actually try to solve some real problem.
  • 11. QUESTION ā€œOh crap, OSCON accepted my talk submission. Now what do I do?ā€ So I looked for a real-world problem to solve.
  • 12. QUESTION ā€œCan BDD be used to write an iPhone app?ā€ Something that wasnā€™t related speciļ¬cally to this conference, or even to the general task of seeing how well BDD and iPhone development ļ¬t together.
  • 13. QUESTION ā€œWhatā€™s on the radio?ā€ It had to be something I would have liked to do anyway, irrespective of conferences and technologies. Something to keep the imagination circuits grounded in reality.
  • 14. JUST PLAYED http://www.25y26z.com So I undertook to write an iPhone app that would let me ā€œbookmarkā€ what radio station is playing any given moment and look the information up later. Yes, Iā€™ve heard of pencil and paper, Shazam, and the Zune. There are tradeoffs that make this app better than those approaches in some ways, and worse in others.
  • 15. HOW THIN IS YOUR GUI? Presenter First Techniqueā€”Brian Marick http://exampler.com/src/mwrc-start.zip Do you need a full-stack GUI test? If your GUI is an ultra-thin, ultra- reliable layer on top of an API, many of your automated tests can bypass the GUI and talk directly to the API.
  • 16. DRIVING A GUI 1.Push the button 2.See what happens At some point, though, itā€™s nice to see the real thing running, top to bottom. And thatā€™s the approach I took with the Just Played app. The two main phases of automating a GUI are simulating events and inspecting controls.
  • 17. DRIVING THE PHONE Matt Gallagher http://cocoawithlove.com/2008/11/ automated-user-interface-testing-on.html Matt Gallagher has done the groundbreaking work on both these fronts. In a series of articles on his Cocoa With Love blog, he discusses how to synthesize touch screen events and interrogate the GUI on an iPhone.
  • 18. touchesBegan:withEvent: touchesMoved:withEvent: touchesEnded:withEvent: Hereā€™s how Mattā€™s technique works. When you tap a button on an iPhone, the Cocoa Touch runtime looks at the buttonā€™s implementation for these three methods and calls them in sequence...
  • 19. touchesBegan:withEvent: touchesMoved:withEvent: touchesEnded:withEvent: ...
  • 20. touchesBegan:withEvent: touchesMoved:withEvent: touchesEnded:withEvent: ...
  • 21. touchesBegan:withEvent: touchesMoved:withEvent: touchesEnded:withEvent: ...
  • 22. WISH WE WERE HERE UITouch *touch = [[[UITouch alloc] init] autorelease]; NSSet *touches = [NSSet setWithObject:touch]; UIEvent *event = [[[UIEvent alloc] init] autorelease]; [view touchesBegan:touches withEvent:event]; [view touchesEnded:touches withEvent:event]; So all we have to do is create the UITouch and UIEvent objects that the functions expect to see, and all will be well, right? Except that Apple didnā€™t provide initializers for these objects. And they have private ļ¬elds that have to be set up just so, or your app will crash.
  • 23. CATEGORIES @interface UITouch (Synthesize) - (id)initInView:(UIView *)view; - (void)setPhase:(UITouchPhase)phase; - (void)setLocationInWindow:(CGPoint)location; - (void)moveLocationInWindow; @end In Ruby, youā€™d write a monkey patch, modifying the vendorā€™s class at runtime. Objective-C offers a slightly more civilized take on the concept, called a category. You can add methods to a third-party class, and spell out the scope in which they apply. Your new methods have access to the classā€™s private ļ¬elds; lucky for would-be GUI testers!
  • 24. - (id)initInView:(UIView *)view { // ... code omitted for brevity ... _tapCount = 1; _locationInWindow = CGPointMake( frameInWindow.origin.x + 0.5 * frameInWindow.size.width, frameInWindow.origin.y + 0.5 * frameInWindow.size.height); _previousLocationInWindow = _locationInWindow; UIView *target = [view.window hitTest:_locationInWindow withEvent:nil]; _window = [view.window retain]; _view = [target retain]; _phase = UITouchPhaseBegan; _touchFlags._firstTouchForView = 1; _touchFlags._isTap = 1; _timestamp = [NSDate timeIntervalSinceReferenceDate]; // ... code omitted for brevity ... } I gather this wasnā€™t easy. It probably involved making an educated guess, reading the crash logs, trying to read Appleā€™s minds, making some tweaks, and watching it crash all over again. Here are just some of the private ļ¬elds whose purposes, or at least required values, Matt was able to divine.
  • 25. DESCRIPTIONS @interface UIView (XMLDescription) - (NSString *) xmlDescriptionWithStringPadding:(NSString *)padding; @end // ---- snippet from implementation: if ([self respondsToSelector:@selector(text)]) { [resultingXML appendFormat:@"n%@t<text><![CDATA[%@]]></text>", padding, [self performSelector:@selector(text)]]; } The same techniqueā€”patching existing classes at runtimeā€”works for implementing the other half of the GUI testing equation. Hereā€™s a glimpse at how categories put a uniform face on runtime descriptions: location, type, caption, and other information for every control on the screen.
  • 26. SCRIPT RUNNER But how do you specify which button gets clicked, and when, and what the expected outcome is? The original approach provides a ScriptRunner object that reads in a list of commands from a text ļ¬le.
  • 27. BROMINE (not the openqa one) Felipe Barreto http://bromine.googlecode.com Mattā€™s original proof-of-concept embedded the test script inside a special build of an app, which ran ā€œheadlessā€ā€”working tests, but no GUI. Felipe Barreto packaged up the code for running with a visible GUI, in the simulator or on the iPhone. The project is called Bromine (as a nod to the Selenium web testing tool), and is drop-in ready.
  • 28. <plist version="1.0"> <array> <dict> <key>command</key> <string>simulateTouch</string> <key>viewXPath</key> <string>//UIToolbarButton[1]</string> </dict> </array> </plist> The test script format for Bromine, like Mattā€™s project before it, is an XML-based property ļ¬le. Itā€™s simple to parse from Cocoa Touch, but it requires re-bundling your app every time you change your test script. Also, adding conditional tests requires custom Objective-C code.
  • 29. curl --url http://localhost:50000 -d "<plist version="1.0"> <dict> <key>command</key> <string>simulateTouch</string> <key>viewXPath</key> <string>//UIToolbarButton[1]</string> </dict> </plist>" So just for fun, I found an open-source Objective-C HTTP server, dropped it into the project, and taught Bromine how to read its commands from the server instead of a ļ¬le. Now you can keep your app running and drive it from curl.
  • 30. require 'tagz' command = Tagz.tagz do plist_(:version => 1.0) do dict_ do key_ 'command' string_ 'simulateTouch' key_ 'viewXPath' string_ '//UIToolbarButton[1]</string>' end end end Net::HTTP.post_quick 'http://localhost:50000/', command Or Ruby.
  • 31. BROMINE + NET = BROMINET http://github.com/undees/brominet I lightheartedly call the result ā€œBrominet,ā€ as a nod to the new-found network connectivity (and because itā€™s missing a bunch of functions the full Bromine library has).
  • 32. COMPATIBILITY 2.2 3.0 3.1 3.2 beta Simulator āœ“ āœ“ āœ“ āœ“ hope iPhone āœ“ āœ“ āœ“ so One of the drawbacks of using categories to set private ļ¬elds is that the vendor might change those ļ¬elds out from under you. Fortunately, the Bromine project has volunteers who ļ¬gure out how to stay on top of OS changes. Iā€™ve been porting their updates over to Brominet.
  • 33. Weā€™ve now got enough pieces to look at some of the test scripts for the Just Played app, top to bottom.
  • 34. story step deļ¬nition ad-hoc API GUI driver The top-level user stories are in Cucumberā€™s test-oriented subset of English. The Ruby code to press speciļ¬c buttons is effectively an ad-hoc, informally speciļ¬ed, bug-ridden API for the app. A layer of glue codeā€” step deļ¬nitionsā€”sits between them. Down in the plumbing, we make angle brackets and post them via HTTP.
  • 35. Feature: Stations In order to keep my day interesting As a radio listener with varied tastes I want to track songs from many stations Scenario: Deleting stations Given a list of radio stations "KNRK,KOPB" When I delete the first station Then I should see the stations "KOPB" s Hereā€™s a story from late in the appā€™s development. As with all the features, I wrote the story ļ¬rst, and the feature later. The ļ¬rst few lines are just commentary, imploring the test to justify its own existence.
  • 36. Given a list of radio stations "KNRK,KOPB" When I delete the first station Then I should see the stations "KOPB" s The real heart of the tests are the Given/When/Then lines.
  • 37. Given /^a list of radio stations "(.*)"$/ When /^I delete the first station$/ Then /^I should see the stations "(.*)"$/ d Cucumber searches among the Ruby step deļ¬nitions youā€™ve provided to ļ¬nd the match for each step.
  • 38. Given /^a list of radio stations "(.*)"$/ do end When /^I delete the first station$/ do end Then /^I should see the stations "(.*)"$/ do end d When Cucumber hits a line of your test script, it runs the block of code attached to the matching step deļ¬nition. We could put HTTP POST requests directly in these blocks.
  • 39. Given /^a list of radio stations "(.*)"$/ do |stations| app.stations = stations.split(',') end When /^I delete the first station$/ do app.delete_station(0) end Then /^I should see the stations "(.*)"$/ do |text| stations = text.split ',' app.stations.should == stations end d But itā€™s often useful to wrap the speciļ¬c button presses up in an abstraction. Rather than say, ā€œpress the button whose caption is ā€˜Delete,ā€™ā€ you say, ā€œdelete something,ā€ and rely on your ad-hoc API to deļ¬ne how deleting happens in the GUI.
  • 40. class JustPlayed def stations=(list) plist = Tagz.tagz do array_ do list.each {|station| string_ station} end end @gui.command 'setTestData', :raw, 'stations', plist end end end a Here are a few pieces of that API. First, for the Given step, weā€™re bypassing the GUI and pre-loading the app with canned data. (In the context of Rails apps, David Chelimsky calls this technique Direct Model Access.) Brominet sees the ā€œsetTestDataā€ command, recognizes it as something not GUI-related, and hands it off to the appā€™s delegate class.
  • 41. class JustPlayed def delete_station(row) @gui.command 'scrollToRow', 'viewXPath', '//UITableView', 'rowIndex', row @gui.command 'simulateSwipe', 'viewXPath', "//UITableViewCell[#{row + 1}]" @gui.command 'simulateTouch', 'viewXPath', '//UIRemoveControlTextButton', 'hitTest', 0 sleep 1 end end a Once the GUI has the canned test data, we can push some buttons. Three things to note: 1) Bromine can scroll controls into view so they can be tapped; 2) since Bromine sees the view hierarchy as one big XML tree, we specify controls using XPath; and 3) simulateSwipe is deprecated ā€”a new version of this slideā€™s code is posted on GitHub.
  • 42. class JustPlayed def stations xml = @gui.dump doc = REXML::Document.new xml xpath = '//UITableViewCell[tag="%s"]' % StationTag titles = REXML::XPath.match doc, xpath titles.map {|e| e.elements['text'].text} end end a To get the result, we just serialize the entire GUI and grub through it from the comfort of the desktop. This process is slow, but allows Brominet to be built on a small set of primitives.
  • 43. module Encumber class GUI def command(name, *params) command = Tagz.tagz do plist_(:version => 1.0) do dict_ do key_ 'command' string_ name params.each_cons(2) do |k, v| key_ k string_ v end end end end Net::HTTP.post_quick "http://#{@host}:#{@port}/", command end end end g All those library functions call into a common class whose responsibility is posting HTTP requests. It uses the Tagz library to build XML requests and Net::HTTP to send them. This part of the code should be reusable in any Brominet project.
  • 44. FAKING THE TIME The canned-data approach is about to rear its head again, as weā€™re going to fake out parts of the physical world.
  • 45. Feature: Snaps In order to find out what song is playing As a radio listener without a mic or data I want to remember songs for later lookup Scenario: Snapping a song Given a list of radio stations "KNRK" And a current time of 23:45 Then the list of snaps should be empty When I press KNRK Then I should see the following snaps: | station | time | | KNRK | 11:45 PM | s The ļ¬rst thing I committed to the repository, even before creating a new Xcode project, was an older version of this story. See the line about the current time of 23:45?
  • 46. Given /^a current time of (.*)$/ do |time| app.time = time end d Without having control over the time, weā€™d have to resort to creating a bookmark and then checking the time right after, with some sort of fuzzy matching.
  • 47. class JustPlayed def time=(time) @gui.command 'setTestData', 'testTime', time end end a The app has to support this testing hook internally and worry about whether itā€™s supposed to use the real time or the fake one. This juggling introduces complexity. But youā€™re seeing the worst case. The preloading of radio stations you saw earlier met with a much happier fate: the support code was useful for implementing save/restore.
  • 48. TABLES One of Cucumberā€™s ballyhooed features is its support for tabular data.
  • 49. Feature: Lookup In order to find out what songs I heard earlier As a radio listener with network access I want to look up the songs I bookmarked Scenario: Partial success Given a list of radio stations "KNRK" And a test server And the following snaps: | station | time | | KNRK | 2:00 PM | | KNRK | 12:00 PM | When I look up my snaps Then I should see the following snaps: | title | subtitle | | KNRK | 2:00 PM | | Been Caught Stealing | Jane's Addiction | s You can draw ASCII-art tables right in your stories.
  • 50. Given /^the following snaps:$/ do |snaps_table| snaps_table.hashes #=> [{'station' => 'knrk', 'time' => '2:00 PM'} ...] # do fancier things, like manipulating columns app.snaps = snaps_from_table(snaps_table, :with_timestamp) end d If a test step comes with its own table, Cucumber passes it into the deļ¬nition block as an object you can poke at. Most of the time, youā€™ll just want to grab its ā€œhashesā€ array, which gives you one hash per row. But you can also do more complicated table processing, like renaming columns.
  • 51. TAGS One more slice of Cucumber I found convenient for this project is the notion of tagging tests as belonging to different categories.
  • 52. Feature: Stations ... @restart Scenario: Remembering stations Given a list of radio stations "KBOO,KINK" And a test server When I restart the app Then I should see the stations "KBOO,KINK" s For instance... Appleā€™s Instruments tool doesnā€™t like it when your app exits. So I tagged all the scenarios that required a restart.
  • 53. cucumber -t~@restart features When you run your tests, you can tell Cucumber to include or exclude particular tags.
  • 54. RESULTS For an investment of about 10% of the projectā€™s time maintaining the test infrastructure, using executable examples served to: 1) drive out the design, 2) catch a couple of minor bugs, 3) help replicate other bugs found manually, and 4) ļ¬nd leaks (mine and othersā€™). Plus, now I know whatā€™s on the radio.
  • 55. } /undees/justplayed The app is available for free on the App Store. The source code is up on the ā€˜Hub and in the ā€˜Bucket.
  • 56. CONVERSATION ā€¢ Barnes and Noble booth During lunch today ā€¢ Meet Authors from the Pragmatic Bookshelf 7 PM tonight, Ballroom A7 ā€¢ undees@gmail.com ā€¢ http://twitter.com/undees If youā€™d like to continue the conversation, catch me in the hall, or come to one of the scheduled events.
  • 57. SHOULDERS OF GIANTS ā€¢ Bromine ā€¢ Pivotal Tracker ā€¢ cocoahttpserver ā€¢ iBetaTest ā€¢ ASIHTTPRequest ā€¢ api.yes.com ā€¢ hg-git Special thanks to yes.com for making their radio playlist API available under a Creative Commons license.
  • 58. CREDITS ā€¢ Ruby icon (c) Iconshock ā€¢ Instrument photos (c) Tektronix ā€¢ Squirrel (cc) kthypryn on Flickr ā€¢ Hand (cc) myklroventine on Flickr ā€¢ Logos (tm) github and bitbucket Writing the software and preparing the material for this talk has been a blast. I canā€™t wait to see what the rest of OSCON has in store for us, and am grateful for our time together this week. Cheers.
  • 59.
  • 60. BONUS SLIDES Here are a few more tidbits that didnā€™t make it into the main presentation.
  • 61. UNIT TESTING RSpec and iPhoneā€”Dr. Nic Williams http://drnicwilliams.com/2008/07/04/ unit-testing-iphone-apps-with-ruby-rbiphonetest/ I experimented a little with using Dr. Nicā€™s rbiphone library to unit-test my Objective-C model classes from Ruby using the RSpec framework. This technique wasnā€™t a perfect ļ¬t for the models in my app, which were so tied to Apple APIs that there wasnā€™t much for the tests to do.
  • 62. SCREEN CAPTURE Google Toolbox for Mac http://code.google.com/p/google-toolbox-for-mac/wiki/ iPhoneUnitTesting Google has a suite of Mac and iPhone development tools. Their iPhone unit test code is geared toward capturing screen images and comparing them against a reference. Heavily graphical apps, or apps that donā€™t use the standard UIKit controls, spring to mind for a technique like this.