Archive for the ‘bdd’ category

Debugging cucumber scenarios in Rubymine

February 1st, 2012

Debugging cucumber scenarios in Rubymine is wonderfully simple. Here’s how i do it:

Create a cucumber step that puts the app into debug mode and pops open the current page that’s being debugged

Screen Shot 2012-02-01 at 16.00.42

I use Rubymine’s gutter breakpoints, and I add the breakpoint after the save_and_open_page method call, so the page pops open before entering the debugger.

Insert the step into whichever scenario i wish to debug

Screen Shot 2012-02-01 at 15.43.25

Run the scenario in debug mode

Screen Shot 2012-02-01 at 15.48.36

Inspect the app’s objects in Rubymine’s Watches

Screen Shot 2012-02-01 at 15.36.27

Pretty sweet! Super easy and extremely useful.

Rubymine’s a great app, and i’m just starting to scratch the surface.

Testing Paperclip generated expiring S3 urls with RSpec, Cucumber and Timecop

December 21st, 2011

The need

I have a Rails app that is using Paperclip to generate expiring urls for files stored in S3. The urls are set to expire after 1 minute. As much as i trust Paperclip and Amazon, I need tests that prove that these generated urls do in fact expire on time, and that visitors to those files after they’ve expired are prevented from accessing the file.

Disclaimer

This has been a bit of a rush, so no doubt i’ll refactor and tidy the code and this post laster today / in the week.

The solution

I’ve used RSpec and Cucumber to check expiring urls that the system generates to ensure they expire successfully. RSpec simply checks that a generated url includes the Expires parameter and it’s value is set exactly to 60 seconds from now. Cucumber goes further than this by uploading files and checking if they are accessible before and after expiration.

RSpec to simply test that the expiration time generated for a link is correctly set to 1 minute

This test simply asks the model containing the attachment (in this case an “Asset” model), how many seconds from now remain before the attachment expires.

Spec

describe Asset do

it “should return an attachment link that expires within 1 minute” do

asset = Factory.build(:asset)
asset.seconds_until_attachment_expires.should == 60

end

end

This depends on a few new methods in the Asset model class, which take care of extracting the Expires param from the expiring url, and comparing to Time.now.

Asset Model Class

First, we create an instance helper method that returns the number of seconds an object’s url has left before it expires

def seconds_until_attachment_expires

Asset.seconds_until_attachment_expires(expiring_attachment_url)

end

I decided to pass the responsibility of calculating this number to a class method. I did this because the Cucumber tests need to request the same calculation for urls that were generated in the past. If they interacted with an instance of the Asset class, by default it would return a new url each time it was asked. So, rather than clutter up the instance method with a decision about whether to issue a new url or return an existing one, i simply passed the responsibility to the class. That seems to work for now, although I might refactor it later.

Next, we create the class level method that calculates time left until expiration. This accepts a url, meaning we can test urls generated now or in the past

def self.seconds_until_attachment_expires(url)

seconds = attachment_expiration_in_seconds_from_epoch(url) – Time.now.strftime(”%s”).to_i
seconds.round

end

This method simply strips the time from the generated url (via the attachment_expiration_in_seconds_from_epoch method) and rounds the value.

def self.attachment_expiration_in_seconds_from_epoch(url)

url.split(”&”).second.split(”=”).last.to_i

end

Clearly, this is tightly coupled to the format of the generated url string, so a cleaner way should be sought. However, for now, this method is only used in the tests and it does work, so it’ll do for the moment.

Finally, to ensure that Rspec, Cucumber and the app all interact with a url generated exactly 60 seconds from now, we create a model instance method that generates the link. All requests for the link call this method.

def expiring_attachment_url

attachment.expiring_url(60)

end

Cucumber to test actual file access via the browser

Cucumber takes things 1 step further. It interacts with all the same methods that we created on the Asset model, but also goes off and uploads attachments and then tries to access them before and after they’ve expired. We use Timecop to create expired urls, and a Cucumber before hook to ensure all scenarios run from the current time by default.

Scenarios

@selenium
Scenario: Viewing an active attachment on an object

Given some object has been created and a plain text file attached
When I visit the object’s attachment url
Then I should see the contents of the uploaded attachment
And I should not see “Request has expired”

@selenium
Scenario: Viewing an expired attachment on an object

Given some object has been created and a plain text file attached
When I visit the object’s attachment url after it has expired
Then I should not see the contents of the uploaded attachment
And I should see “Request has expired”

features/support/hooks.rb

Before do

Timecop.return

end

NB: For the sake of completeness (even though we’re not calling Timecop from our Rspec specs), to be completely satisfied that Timecop isn’t affecting our specs in any unexpected way, we add the same to spec_helper.rb too:

spec/spec_helper.rb

config.before do

Timecop.return

end

config.before do
Timecop.return
end

steps

This is where Timecop offers a wonderfully simple way of generating expired urls.

And /^I visit the question’s attachment url after it has expired$/ do

#First, go back in time 2 minutes and generate the expiring url, and make sure it’s set to expire in 1 minute
Timecop.freeze(Time.now – 2.to_i.minutes) do

@url = current_object.asset.expiring_attachment_url
Asset.attachment_expires_in(@url).should == 60

end

#Next, return to the current time and make sure the previously generated expiring url has now been expired for 1 minute
Timecop.return
Asset.attachment_expires_in(@url).should == -60

#Finally, go visit the expired url
visit @url

end

When run, cucumber correctly reports that expired urls result in the user seeing the message “Request has expired”, and non-expired urls correctly provide access to the uploaded file.

Summary

Although a rough and ready solution, and most likely needing refactoring, it does provide us with a way to test expiration of uploads to S3.

I hope you found this useful.

Setting up Merb, Cucumber and Webrat (and friends) on Snow Leopard

September 4th, 2009

My upgrade to Snow Leopard killed my Merb, Cucumber and Webrat setup so i had to start afresh. That was the bad. The good is that some manual hacks that were required in Leopard are no longer necessary, meaning I can rely on direct gem installations.

Here’s what i did:

  1. Install Ruby and Gems
    Follow instructions on http://hivelogic.com/articles/compiling-ruby-rubygems-and-rails-on-snow-leopard/
  2. Install merb, rspec, cucumber, merb_cucumber and mongrel and dependencies
    sudo gem install merb rspec cucumber roman-merb_cucumber mongrel term-ansicolor treetop diff-lcs nokogiri do_sqlite3
  3. Install webrat
    sudo gem install hoe hpricot webrat
  4. Fix Firefox bug with Snow Leopard
    For some reason the libsqlite3.dylib  library in FireFox 3.5.2 is out of date and breaks cucumber under Snow Leopard. Thankfully, it’s a simple fix:
    mv /Applications/Firefox.app/Contents/MacOS/libsqlite3.dylib /Applications/Firefox.app/Contents/MacOS/libsqlite3.dylib.orig
    cp /usr/lib/libsqlite3.dylib /Applications/Firefox.app/Contents/MacOS/libsqlite3.dylib
  5. Install Selenium
    sudo gem install Selenium
    sudo gem install selenium-client
  6. Install the textmate cucumber bundle
    http://github.com/bmabey/cucumber-tmbundle/tree/master

Deprecated Instructions on Snow Leopard that were required on Leopard

The following ugly hacks were required on Leopard with it’s default Ruby installation. These are no longer required (at least on my machine) on Snow Leopard:

Manual hack of Selenium

http://wiki.github.com/aslakhellesoy/cucumber/setting-up-selenium

As the instructions recommended replacing the Selemium RC jar file () in the installed gem with one from the Selenium website, i had to find out where the gem had installed. Thankfully, gem -h pointed me toward gem help commands and from there i ran gem environment – This told me where gems are installed locally and i found that Selemium RC had been installed into /Library/Ruby/Gems/1.8/gems/Selenium-1.1.14/ I replaced as advised and then ran selenium from within the app root and all worked fine, using the replacement. I then downloaded and ran the test selenium code from http://github.com/aslakhellesoy/cucumber/tree/master/examples/selenium running selenium in a different console and then running cucumber examples/selenium/features/ . It worked a treat and booted up selenium as required. Great!

Manual hack installation of webrat

Download http://github.com/gwynm/webrat/tree/master tar file. Git clone doesn’t work

sudo gem install hoe hpricot
cd downloaded and untarred file
rake gem
sudo gem install pkg/webrat-0.2.1.gem

Presenting BDD Story-driven delivery to project and account managers

April 15th, 2009

Today, i had the true pleasure of presenting my view of how BDD stories offer real business value to project delivery, quality and to the lives of everyone on a software delivery project.

Part 1: Collectively clarify what happens on projects now (on projects that do not use stories)

It was a highly interactive session and i first asked attendees to collectively draw on a whiteboard the project process as they saw it, with all of the project’s actors, products and interactions.

What was drawn resembled a kind of mashup of a UML activity diagram with swim lanes & a gantt chart. It showed what the individual actors in the process did and when, and who fed into who in the process.

Part 2: Clarify what documents are written and the associated risks and costs

When the whiteboard was complete, I asked the attendees to consider a number of things:

  1. At what points during the process are documents produced and by who?
  2. Of those documents produced, which are directly focused on the business and user requirements?
  3. Of the many producers of those documents, which of these producers had their focus on the business and user requirements?
  4. Of the many documents produced, which were open to interpretation and translation?
  5. What are the perceived risks of having so many documents and periods of documentation translation?

When this work was complete, a few points became clear to the group:

  1. The project team, as defined on the whiteboard, was greatly separated into areas of expertise and each was concerned about their area of expertise
  2. Interactions between actors were mostly through written documents
  3. Few actors following this project process retained a direct focus on the business and end user requirements
  4. A lot of documentation was being written and much of it was being duplicated, at times to protect actors within the process
  5. Vast amounts of document interpretation and translation was going on to produce each document

Part 3: Consider stories

After this part of the session was complete, i gave some examples of the wonderfully simple story DSL and then suggested that many of these documents could be replaced by stories and scenarios:

  1. I explained that stories can be used to clarify both high-level requirements and detailed solution definitions. I described how stories can be expanded through the use of scenarios.
  2. I described how everyone on the project, including the client, can understand the wonderfully simple DSL and contribute to the bank of stories.
  3. I then came in with the ace :) Stories can be used to drive automated browser tests! Man, they fell off their seats at the point!

It was an awesome session. A lot was discussed and understood.