Showing posts with label rspec. Show all posts
Showing posts with label rspec. Show all posts

Thursday, August 09, 2007

Story-level BDD specing using rBehave

For those in the BDD world that have been asleep at the wheel over the last month or two, Dan North has released rBehave which wraps around RSpec and allows developers to spec their code at the story level, which has been missed in RSpec (though I don't know what I would do without RSpec).

Dan mentions that he is discussing integrating his work into RSpec at some point in the future, but in the meantime Dan's rBehave framework is currently at version 0.3.0 and is available for install as a Ruby Gem as usual: sudo gem install rbehave

Dan gives an example of rBehave using a bank transfer story on his blog. Below I give another example of authenticating a user:

require ‘rubygems’
require ‘rbehave’
require ’spec’ # for "should" method

require ‘user’ # the actual application code

Story "authenticate user",
%(As a user of the system I wish to authenticate and access my account information.) do

Scenario "correct password is supplied but user has been suspended" do
Given "my user account is created", User.new do |user|
@user = User.new
end
Given "my username is", "myusername" do |username|
@user.username = username
end
Given "my password is", "mypassword" do |password|
@user.password = password
end
Given "my user account has been suspended", true do |suspended|
@user.suspended = suspended
end
When "I attempt to authenticate with", "mypassword" do |password|
@authenticated = @user.authentiate?(password)
end
Then "my user account should respond with authentication status of", false do |status|
@authenticated.should be(status)
end
end

Scenario "correct password is supplied and user is active and verified" do
Given "my user account is created", User.new
Given "my username is", "myusername"
Given "my password is", "mypassword"
Given "my user account has been suspended", false
Given "my user account has been activated", true do |activated|
@user.activated = activated
end
When "I attempt to authenticate with", "mypassword"
Then "my user account should respond with authentication status of", true
end

# A few other scenarios go here, like non-activated user (but not suspended), etc.
# Left as exercise to the reader.
end

Please note, i do not need to restate the Given, When or Then blocks passed in after they are defined once!

Monday, July 02, 2007

Marrying Autotest with RSpec on Gnome

For those that don't know marrying Autotest with RSpec is a God send and I highly recommend it for pure Ruby as well as Rails application development.

On the Mac OS X and KDE desktop environments there are builtin Autotest extensions that visually flag erroneous or successful RSpec runs with a standard desktop notification message.

For Gnome users on Linux running Autotest with RSpec you may have seen a couple of ${HOME}/.autotest customizations out there that don't work with RSpec v1.x+.

Below is my ${HOME}/.autotest that works with RSpec 1.0.5:

require('autotest/redgreen')
require('autotest/timestamp')

module Autotest::GnomeNotify

# Time notification will be displayed before disappearing automatically
EXPIRATION_IN_SECONDS = 3
ERROR_STOCK_ICON = "gtk-dialog-error"
SUCCESS_STOCK_ICON = "gtk-dialog-info"

RE_RSPEC_SUMMARY = Regexp.new(/(\d+) examples, (\d+) failure/)

class << self
# Convenience method to send an error notification message
#
# [stock_icon] Stock icon name of icon to display
# [title] Notification message title
# [message] Core message for the notification
def notify(stock_icon, title, message)
options = "-t #{EXPIRATION_IN_SECONDS * 2500} -i #{stock_icon}"
system "notify-send #{options} '#{title}' '#{message}'"
end

def compose_message(at)
specs, failures = 0, 0
at.results.scan(RE_RSPEC_SUMMARY) do |s, f|
specs = s.to_i
failures = f.to_i
end
"#{specs} specs, #{failures} failures"
end
end

Autotest.add_hook :red do |at|
notify ERROR_STOCK_ICON, "Some specs failed.", compose_message(at)
end

Autotest.add_hook :green do |at|
notify SUCCESS_STOCK_ICON, "All specs passed. Have a beer!", compose_message(at)
end
end

I assume there this will work with RSpec 1.x, but I have only tested with RSpec 1.0.5.

Enjoy!

Sunday, July 01, 2007

RSpec "should_yield"?

While writing some specifications for the 0.2.0 version of Twitter4R (soon to be released - honestly), I realized there was no clean way to do #should_yield, so I thought I would offer my suggestion for the developers of RSpec to critique as appropriate.

Assume we have a method signature that takes a block and should yield an object to the block given (if one is given). Something like the following:

class Client
class << self
def configure(&block)
# implementation of Client.configure
end
# rest of class methods for Client class go here...
end
# rest of instance methods for Client class go here....
end

Where usage for the Client.configure method should look something like the following:

Client.configure do |conf|
conf.proxy_host = 'myproxyhost'
conf.proxy_port = 8080
conf.proxy_user = 'me'
conf.proxy_pass = 'mypass'
end

The object yielded to the block given is an instance of Config which has settable attributes: proxy_host, proxy_port, proxy_user, proxy_pass.

The question is how do we write RSpec specifications for this?

First we need to define what we want to specify for the expected behavior. My first attempt is to describe (in English) that:
  • Client.configure should
    • accept a block as argument
    • yield a Config instance to the given block

The way I did this in the Twitter4R (open source Ruby library for the Twitter REST API) project specifications was something like the following:

describe Client, ".configure" do
before(:each) do
@has_yielded = false
@block = Proc.new do |conf|
conf.is_a?(Config).should be_true
@has_yielded = true
end
end

it "should accept a block as argument" do
lambda {
Client.configure {}
}.should_not raise_error
end

it "should yield a Config object to given block" do
Client.configure(&block)
@has_yielded.should be_true
end

after(:each) do
@has_yielded, @block = nil
end
end

What I would really like to do something like the following instead:

describe Client, ".configure" do
it "should accept a block as argument" do
lambda {
Client.configure {}
}.should_not raise_error
end

it "should yield a Config object to given block" do
lambda {
Client.configure(&spec_block(1))
}.should yield_with(Config)
end
end

I haven't extended the Proc class to this point, but if/when I do I will post it to this blog and perhaps also to the RSpec mailing list.

If you have suggestions on how to improve the more manual way (the first RSpec code listing above), please do let me know. I have not yet played around with inheritable describe blocks yet, but I have hunch that would DRY up some of the code for if needed for many contexts.

Alternatively if you have suggestions on how to improve the second RSpec code listing (directly above) to be more clear, please do let me know that too. I realize the developers of RSpec don't want to have too many #should_XXX methods, so I am open to suggestions (since I also agree with that notion).

UPDATE: I created a new feature request and dialog with RSpec developers at: [#11949]

Saturday, May 19, 2007

RSpec 1.0.0 Finally Here!

Thanks to the RSpec team for finally delivering RSpec, which I have been excited about for quite some time.

Having about 5 projects (2 pure Ruby, 3 Rails) using RSpec 0.8.2 and 2 other projects still on (0.7.5) I was a little worried about migrating to the new RSpec 1.0.0 compliant API, which is a fair bit different than some of what was allowed in 0.8.2 and prior.

However, the RSpec developers in all their wisdom foresaw this problem and wrote spec_translate where you simply give it the from directory and to directory in that order on the command line. Now it doesn't do everything, so don't expect too much, but it will convert all the context, setup, specify and teardown blocks to describe, before, it and after blocks respectively with very little fanfare or effort required from us.

The gotchas for those of us that ignored the Test::Unit Cheat Sheet on the website for recommended RSpec usage (I am going to own up to it, yes):
  1. .should.eql expected_value becomes .should eql(expected_value)
  2. .should.not... becomes .should not...
  3. .should.be expected_result becomes .should be(expected_result)

Let me know if you find different varieties of the above API differences.