RSpec: It Should Behave Like
I was going through an older project of ours and cleaning up some specs and noticed how often we were doing the same thing in several places. When we started the project, we didn’t get the benefits of shared groups. Now that we have some time to go through and update some of our older specs, I’ve been trying to take advantage of the features currently available in RSpec. One feature that I haven’t seen a lot of mention of by people is shared groups, so I thought I’d take a few minutes to write up a quick intro to using it.
To pick some low-hanging fruit, let’s take an all-too-familiar method, which you might be familiar with… login_required. Sound familiar? Have you found yourself stubbing login_required over and over throughout your specs?
describe Admin::DohickiesController, 'index' do
before( :each ) do
controller.stub!( :login_required )
Dohicky.should_receive( :paginate ).and_return( Array.new )
get :index
end
...
endIf you’re requiring that a user should be logged in when interacting with most of the application (as in the case of an administration section/namespace), you might want to consolidate some of your work into one shared specification group. The basic premise behind this is that you can write a typical describe block and load it into any other spec groups that you need. For example, in our case, we’ll need to stub login_required in several places. We can set this up in one shared group and reference it wherever necessary.
For example, here is what we’ll start off with.
describe "an admin user is signed in" do
before( :each ) do
controller.stub!( :login_required )
end
end
describe Admin::DohickiesController, 'index' do
...However, the new describe block isn’t accessible from the block at the bottom of the example… yet. To do this, we just need to pass the option: :shared => true as you’ll see in the following example.
describe "an admin user is signed in", :shared => true do
before( :each ) do
controller.stub!( :login_required )
end
endGreat, now we can reference it by referring to it with: it_should_behave_like SharedGroupName. In our example above, this would look like:
describe "an admin user is signed in" do
before( :each ) do
controller.stub!( :login_required )
end
end
describe Admin::DohickiesController, 'index' do
it_should_behave_like "an admin user is signed in"
before( :each ) do
Dohicky.should_receive( :paginate ).and_return( Array.new )
get :index
end
...
end
describe Admin::DohickiesController, 'new' do
it_should_behave_like "an admin user is signed in"
before( :each ) do
@dohicky = mock_model( Dohicky )
Dohicky.should_receive( :new ).and_return( @dohicky )
get :new
end
...That’s it! Pretty simple, eh? We can now reference this shared group in any describe blocks that we want to. A benefit to this approach is that we can make change the authentication system (say, we decide to switch it entirely and/or even just change method names, set any other prerequisites necessary when an admin is signed in), we’ll have a single place to change in our specs. (tip: you can put these in your spec_helper file)
You can learn more about it_should_behave_like and other helpful features on the RSpec documentation site.
If you have any suggestions on better ways of handling things like this, please follow up and share your solutions. I’m always looking to sharpen my tools. :-)
Related Posts
Alan Cooper @ Agile2008 slides 3
Alan Cooper, author of About Face, has slides from his presentation at Agile 2008 online.
If anybody knows if there is video of this talk, please let me know. :-)
Here are a few skitches from the slideshow.
The Art of Delivery, part 2 3
Two years ago, I wrote an article titled, The Art of Delivery. I wanted to post a few updates based on how our process has evolved since then (and continues to).
Over the past few years, we’ve been fortunate enough to work on quite a diverse collection of projects. This has enabled us to work with many different clients and solicit feedback on our process. This has given us an opportunity to evolve a set of best practices that fulfills the long-term project goals/budgets of our client while making sure that we’re able to maintain a design and development process that is agile.
As I’ve mentioned in previous posts, our team typically bills work per-iteration on projects rather than per-hour or a flat-bid per-project. Since iterations are bite-sized pieces of the entire project and limited to 1-2 weeks, our teams estimates are much more accurate and we’re able to keep things rolling and on track.
The basic structure of our project looks like this.
- A Project has many releases
- A Release has many iterations
- An Iteration has many deliverables
- A Deliverable has many tasks
Before we begin working on an iteration, we outline a set of goals that we want to create solutions for. This process comes out of discussions between our client and us until we agree on what is the highest value/most critical to the success of the project, based on our shared understanding of where we are today. These goals translate into Deliverables, which in a typical iteration might require Interaction Design, Interface Design, or Development. We tend to break our process up into stages so that Interaction Design on Module XYZ would be implemented in a following iteration. This is because it’s unrealistic to expect someone to provide an accurate estimate on how long it’ll take to implement something before you know how people will interact with it.
Within any given iteration, our team is spread across several sets of deliverables. As a team, we breakdown these deliverables into smaller sets of tasks. It’s our aim to keep tasks smaller than a full days worth of work as it’s much easier to measure progress across the iteration when we can track tasks at a granular level.
Essentially, tasks are the individual steps needed to achieve these goals. We don’t go out of our way to list each one of those during an estimate process as some tasks take less time than it takes to generate an estimate for them. Each person providing estimates should avoid getting too granular and aim to find a good balance that compliments their workflow.
Like most things… mileage may vary.
Through this process, we can get calculate the estimated costs for each deliverable, which then provides us an cost for the entire iteration. In addition to deliverables, we also budget a set of hours/days so that we can be compensated for handling small requests, bug fixes, and project management. It’s important to factor these things into your process.
In future posts, I’ll discuss how we’re handling this process while working on multiple projects… as that’s where it can chaos can start if you’re not careful. ;-)
How does your team work? As we’re always evolving our process in an effort so that we can be more efficient and speed up our delivery cycle, I’d love to learn from those in the community.
RubyURL meets Zombies!
Last Friday, Greg Borenstein sent me a link to ZombieURL after it got launched. The folks at Bottlecap Labs took RubyURL and threw in Zombies… the rest you’ll have to see for yourself.
There… I warned you.
You can check out the source code for ZombieURL on GitHub
You can also grab the underlying source code for RubyURL on GitHub.
I’d love to see what other fun things people come up with to do with RubyURL.
Tip: Link to Unimplemented 12
Throughout our design and development process, we’re working around areas of the site that are not yet implemented but we also want to be able to allow our clients to demo their application. In an effort to manage their expectations, we need to be careful about what we link to. If a page/widget isn’t ready to be demo’d yet, we should avoid providing pathways to get interact with or navigate there. However, when we’re implementing HTML/CSS for pages, it’s sometimes makes sense to not hide certain things on the screen.
For example, let’s suppose that you’re working on the primary navigation of an application. You know what the other sections are going to be, but you’ve only implemented a few of them so far. Your HTML/CSS person is working on the design for the navigation and wants to have them be proper links… even to pages that don’t yet exist.
One option, which is quite common, is to provide a link with href="#". This works to some extent, but when people click on things, they naturally expect something to happen in response.
This approach doesn’t mesh well with our team as we don’t really want to field any questions like, “the navigation links are all broken. Nothing happens!”
So, a pattern that we’ve been using for a while is to trigger a javascript alert for every link within an implemented area that is linking to something that isn’t yet implemented.
Let’s take a really basic javascript function like:
# public/javascripts/application.js
function unimplemented() {
alert("NOTICE\n\nThis feature is not implemented yet. Please check back again soon!");
}
This allows us to do the following:
<a href="javascript:unimplemented();">link text</a>
When someone clicks the link, they’ll see a typical javascript alert message. This informs our clients/beta testers that we’re paying attention to what works and what doesn’t.
Let’s take it a step further and push this into a view helper.
# app/helpers/application_helper.rb
def link_to_unimplemented( link_text, *args )
link_to_function( link_text, 'unimplemented()', *args)
end
Now, we’re able to use link_to_unimplemented and pass any arguments that you’d pass to the default link_to view helper.
<%= link_to_unimplemented( 'link text', { :class => 'link_class_name' } ) -%>
Now our web designers can go about their work and use this helper as necessary.
An nice benefit for doing this is that we have a pattern that we follow so that we can rely upon to make sure that we don’t forget anything. This is the equivalent of adding @TODO@s throughout our code base.
If we search through app/views for ‘link_to_unimplemented’ we should be able to prevent missing any broken links. In the next screenshot, I’m using grep with colorized matches.
As you can see, we have something left to implement in that area of the application. :-)
This has been one of those lightweight patterns that we’ve been able to adopt and it’s definitely helped manage the expectations of our clients throughout our development process.
I’d love to hear your thoughts on this. How does your team handle things like this?
Related Posts
ShortURL 0.8.4 released and gets a new mainainer... me! 2
Earlier today, Vincent Foley was kind enough to hand over maitenance of the the ShortURL project on RubyForge to me. He first released it back in 2005, which I blogged about as RubyURL was the first shortening service that it supported (and is the default). Unfortunately, the release of RubyURL 2.0 broke backwards compatibility and Vincent wasn’t maintaining it anymore. So, earlier, I decided to patch this and got a new version released that now works with the current RubyURL site.
While working on the code, I decided to extend the compatible services to include moourl and urlTea.
These updates are available in ShortURL version 0.8.4.
Install the ShortURL gem
Installation is a snap… (like 99.7% of rubygems…)
~ > sudo gem install shorturl Password:
Successfully installed shorturl-0.8.4
1 gem installed
Installing ri documentation for shorturl-0.8.4...
Installing RDoc documentation for shorturl-0.8.4.
Using ShortURL
The ShortURL gem provides the ShortURL library, which you can use from any Ruby application.
Using the ShortURL library
~ > irb
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'shorturl'
=> true
irb(main):003:0> ShortURL.shorten( 'http://www.istwitterdown.com' )
=> "http://rubyurl.com/P9w"
As you can see…it’s really straight forward.
Let’s try it with a few other services.
irb(main):004:0> ShortURL.shorten( 'http://www.istwitterdown.com', :moourl )
=> "http://moourl.com/fvoky"
irb(main):005:0> ShortURL.shorten( 'http://www.istwitterdown.com', :tinyurl )
=> "http://tinyurl.com/2t3qmh"
Using the shorturl command-line tool
Many people don’t know that ShortURL provides a command-line tool, which you can use after installing the gem.
~ > shorturl http://istwitterdown.com
http://rubyurl.com/Lwk
If you’d like to see more services provided than the ones listed here, please submit feature requests and/or patches on the rubyforge project.
ShortURL Documentation
To see the latest documentation for the project, please visit:
My favorite part about this? My rbot plugin for RubyURL works again!
Happy URL-shortening!













