5:20 PM EDT Tuesday, September 16 2008
Earlier this year at the Gotham Ruby Conference, Brian Helmkamp gave a talk on Story Driven Development. It's a fantastic talk, I suggest that everyone view it. In it he explains how to use RSpec Users Stories combined with Webrat for full-stack executable scenario testing. He also covers what value executable scenarios provide and where executable scenarios fit into your overall web application testing strategy. This article provides you with a more detailed how-to of the steps required to get up and running with Story Driven Development with Rails.
In this article I don't go to much into the details of how story runner works, so I would say another prerequisite for this article is Geoffrey Grosenbach's RSpec User Stories available on PeepCode.
So let's say we are building a rails version of this blog. First steps are the basic rails setup:
$ rails blog
$ cd blog
$ script/plugin install git://github.com/dchelimsky/rspec.git -r 1.1.4
$ script/plugin install git://github.com/dchelimsky/rspec-rails.git -r 1.1.4
$ script/generate rspec
$ script/plugin install git://github.com/ratnikov/fixture_replacement2.git
That gives us an rspecified Rails app, which has a stories directory, all ready for us to put our stories in. Fixture Replacement is an implementation of the factory pattern for creating test data, which is an alternative to using fixtures data in yaml files. So let's create the first story in stories/view_articles_story.txt:
Story: View Articles
As a reader of the blog
I want to view articles
So that I can read what you have to say
Scenario: display most recently published articles
Given 5 articles have been published
When I visit the homepage
Then I should see the articles
A couple of things to point out. First, the naming convention I'm going with is to put the plain text story in <story_name>_story.txt and the story runner file in <story_name>_story.rb. Next I'd like to talk a little bit about this story. I choose to write this story first because it is the most important feature in the application. Generally I like to try to follow this rule. When writing a stories, always write a story for the next most important feature.
So in order to use this story, we need a runner. So let's put this into stories/view_articles_story.rb:
require File.dirname(__FILE__) + "/helper"
run_story :view_articles
This is about concise as it gets for this file. The magic happens in stories/helper.rb. Add this to the bottom of that file:
def run_story(story_name, options={})
with_steps_for(story_name) do
run(
File.join(File.dirname(__FILE__), "#{story_name}_story.txt"),
{ :type => RailsStory }.merge(options)
)
end
end
What this does is allow you to call run_story to run a story. It assumes by default that you will want to use the steps that are defined in the same file with the same name as the story. We'll probably enhance run_story in the future to do more stuff, but this is all we need for now. Now you can run either stories/all.rb or stories/view_articles_story.rb and you should get:
Running 1 scenarios
Story: View Articles
As a reader of the blog
I want to view articles
So that I can read what you have to say
Scenario: Homepage
Given 5 articles have been published (PENDING)
When I visit the homepage (PENDING)
Then I should see the articles (PENDING)
1 scenarios: 0 succeeded, 0 failed, 1 pending
Pending Steps:
1) View Articles (Homepage): 5 articles have been published
2) View Articles (Homepage): I visit the homepage
3) View Articles (Homepage): I should see all 5 articles
So we now have yellow (a.k.a pending) scenarios, so step 1 is done. The next step is to write step matchers, which should make our scenario fail. So we add this to stories/view_articles_story.rb:
steps_for(:view_articles) do
Given "$count articles have been published" do |count|
@count = count.to_i
@count.times {|n| create_article(:title => "Article ##{n}") }
end
When "I visit $path" do |path|
get path
end
Then "I should see the articles" do
@count.times do |n|
response.should have_tag("h2", "Article ##{n}")
end
end
end
Make sure to add that before the run_story :view_articles line. Now you should have red (a.k.a failing) scenarios, that look something like this:
FAILURES:
1) View Articles (Homepage) FAILED
NoMethodError: undefined method `create_article' for
#<ActionController::Integration::Session:0x208bfe0>
Make sure they are failing for the right reason. For example, when I first wrote this, I forgot that all the of variables are captured as strings and I left off the call to .to_i on count. I get failing specs, but not because I haven't implemented the code, because my steps are wrong. Remember, tests can have bugs too.
Let's take a look at each of these steps. Inside the given step, we are calling create_article. Now as you noticed from the failure message, there is no create_article method yet. Fixture Replacement will take care of defining that for us once we have the articles model created and example_data.rb created. In the when step, we are capturing the path the user is trying to access, and then using the same rspec controller helper method we would use if this were a controller spec. get /some_path goes through the routing to call our controller with an HTTP get. Finally, in the then step, we just check that the HTML in our response has an H2 element with the title of the article in it, for each of the articles we created in the given step.
So now on to step 3, which is to write the code to make it go green. To make this easy, we'll use the rspec scaffold generator:
$ script/generate rspec_scaffold Article title:string body:text
$ rake db:migrate
$ rake db:test:prepare
So if we run stories/view_articles_story.rb again, we get the same error. That's because we still haven't told Fixture Replacement about our article model. So create the file db/example_data.rb and put this in it:
module FixtureReplacement
attributes_for :article do |a|
a.title = "First Post!"
a.body = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit..."
end
end
This defines the minimum set of data required to create a valid article. You can override any value by simply passing it in the hash, as we are doing in the given step. Fixture Replacement gives you two methods for each model, which are in this case, create_article and new_article, which do pretty much what you would expect. You can read more about Fixture Replacements in the README.
Last but not least, we have to include FixtureReplacement in our RSpec Story Runner, which is as simple as adding the line include FixtureReplacement somewhere near the top of stories/helper.rb, after all the other require statements.
So now if we run our story, we get something like:
1) View Articles (Homepage) FAILED
Spec::Expectations::ExpectationNotMetError:
Expected at least 1 element matching "h2", found 0.
<false> is not true.
Which is still failing, but it's progress. Obviously the problem now is that our view isn't outputting H2 elements for the article titles. That's an easy fix which I'll leave as an exercise to the reader (hint: modify the default route, edit the articles index ERB template).
One might argue that checking for H2s is checking the implementation details, which could be fragile if our designer decides to use DIVs instead, for example. That decision is up to you, personally I like to produce the correct semantic markup, have my tests validate that and apply CSS from there. If you'd rather your tests be less coupled to the DOM structure, you could just change your expectation in the then step to response.should have_text("Article ##{n}").
Hopefully that gets you up and running with Fixture Replacement and RSpec User Stories. Tune in for Part II of the series, where we cover more complex interactions with Webrat.
Posted in
Technology |
Tags
RSpec, Rails, FixtureReplacement, Ruby, StoryRunner |
1 Comments
12:37 AM EDT Saturday, September 6 2008
Once upon a time, there was a web MVC framework called Struts. Struts was one of the original catalysts of web frameworks based on the MVC pattern, but it was written in Java and required copious amounts of XML to configure your application. One of the many things defined in the XML were the "Action Mappings". An Action Mapping essentially mapped a specific URL pattern to a specific Java class that would be responsible for handling that request.
Then came Ruby on Rails, which eliminated the need for these XML configuration files by using Convention Over Configuration. The way this works in Rails is that if a request is sent to the url /users/new, Rails will call the new method of the UsersController class to handle the request. Rails has a feature called routing that is used to map unconventional url patterns to specific controller actions. Then came RESTful Rails and the convention over configuration was gone.
With RESTful Rails, the request path simply represents what you want to perform an operation on, the Resource, and the HTTP method specifies what you want to do. This all makes sense, but one problem is that RESTful urls do not conform to the url convention. This means every action must be defined in the routing. A shortcut was added to the routing to allow one expression to define the 7 typical methods the controller for a resource will have, but any additional actions must be explicitly defined.
I've been recently working with an experienced Java developer with some familiarity Rails. He was confused by the way RESTful routing works and what paths the named route methods would generate. When he asked what benefits all this provides over the original /controller/action/id pattern, where only non-standard routes had to be mapped, I struggled to find any. I realized that I was doing this just because it was now the "Rails Way". He said this reminds him of Struts, and after some arguing and thinking about it, I realized he was right.
So it seemed unbelievably coincidental that we both found ourselves together today in a talk titled Unconvental Wisdom by Bruce Tate. In today's talk, Bruce pointed out that RESTful Rails adds complexity to Rails, which makes it harder to explain how Rails works to newcomers to Rails. I'm very interested to see how this talk is received by the Rails community, so if you weren't at the talk today, look for it on Confreaks in the next few weeks.
So looking back on RESTful Rails applications that I've developed, if they reach even a level of medium complexity, you end up with 50 lines or so in the routes.rb, with at least a handful of nested and custom routes, in addition to all of the resources. Imagine this as the admin interface for a simple blog:
map.namespace(:admin) do |admin|
admin.resources :articles, :has_many => :comments, :member => { :publish => :post }
admin.resources :categories, :has_many => :articles
admin.resources :comments, :belongs_to => :article
admin.resources :tags, :has_many => :articles
end
If you are an experienced Rails developer, you can decipher this right away. But is this really easy understand? Or wouldn't convention over configuration be easier:
map.connect "/admin/:controller/:action/:id"
And what about named routes like new_admin_article_comment_path(@article)? Is that really more clear than admin_path("/comments/new", :article_id => @article)"? Do you really care if the urls are /comments/new?article_id=1 or /articles/1/comments? Maybe I missing something, but I'm starting to like the sound of idea of having almost nothing in my routes and not calling dynamically generated methods everywhere to just to build simple paths. After all, no code is faster than no code.
Posted in
Technology |
Tags
Rails, Ruby, REST |
14 Comments
2:33 PM EDT Saturday, August 30 2008
A small Rails technique that I've been using lately is concerned_with. I first saw Matthew Bass use it on the => 3-2-1, but I've gleaned from a couple of articles on the web that it was invented by Rick Olsen.
The purpose of concerned_with is to break up larger models up into concerns. Let's say you have a User model, and you have several macros, includes, validations, callbacks, class methods and instance methods all related to authentication, maybe sort of like this:
class User << ActiveRecord::Base
include AuthenticationSupport
validates_presence_of :login
validates_uniqueness_of :login
before_create :set_default_password
class << self
def authenticate(login, password)
login == "root" && password == "too_many_secrets"
end
end
def set_default_password
self.password = "pa$$w0rd"
end
end
As your model grows in complexity, eventually what will happens is that you will have more validations, callbacks, class methods, instance methods, etc. They will get mixed in with the authentication ones, app/models/user.rb will grow to become 500 lines of code and it will be hard to keep track of what's what. Instead, just put this into app/models/user.rb:
class User << ActiveRecord::Base
concerned_with :authentication
end
And then throw all of that into app/models/user/authentication.rb:
class User
include AuthenticationSupport
validates_presence_of :login
validates_uniqueness_of :login
before_create :set_default_password
class << self
def authenticate(login, password)
login == "root" && password == "too_many_secrets"
end
end
def set_default_password
self.password = "pa$$w0rd"
end
end
So now your app/models/user.rb is clean and can have other stuff in there, and all the authentication related stuff gets loaded from app/model/user/authentication.rb. All you have to do to enable this is is throw this snippet into an initializer, like config/initializers/concerned_with.rb:
class << ActiveRecord::Base
def concerned_with(*concerns)
concerns.each do |concern|
require_dependency "#{name.underscore}/#{concern}"
end
end
end
Posted in
Technology |
Tags
Rails, Ruby |
21 Comments
2:08 PM EDT Friday, August 15 2008
This week I took a brief hiatus from my normal job at BrowserMedia to spend a week on a 3-2-1 project with Hashrocket with fellow guest stars Hampton Catlin and Matthew Bass. It was a great experience for many reasons.
One of the key factors that made this 3-2-1 possible is that not only do we all use good tools, but we all use the same good tools. All of us use Macs, Textmate, Git, Rails, RSpec, Restful Authentication and Haml. This allowed us to hit the ground running. All the basic infrastructure needed for building a web app was in place, so were able to get going and focus on the core functionality of the app.
Pair programming, at least to the degree that it is done at Hashrocket, was a new experience for me. Every developer works in a pair programming setup, with a laptop hooked up to a 30-inch display and two keyboards and two mice. Having a large monitor, two keyboards and two mice may seem like a luxury, but it really helps you get into the flow of pair programming.
Another core philosophy at the heart of the Hashrocket methodology is test-driven development (TDD). TDD is something that most agile development shops strive to do, but sometimes fall short. I believe that two of the main reasons many teams struggle getting TDD right are tight deadlines and lack of developer expertise and discipline. All developers who try to do TDD have experienced this at some point. You've got a feature that needs to be cranked out ASAP, you're not exactly sure how test it, so you just dive into the code and pound it out. My experience at Hashrocket has taught me that that all too common scenario can be avoided by practicing pair programming and TDD together.
One pair programming technique that makes TDD easier is ping pong pair programming. When doing ping pong pair programming, when you sit down to build a feature, the first person in the pair writes the test. Next, the second person in the pair writes the code to make the test pass. Then the second person writes a test for the next feature, and the first person in the pair implements it. By repeating this process throughout day, you have several benefits. First of all, it avoids the scenario where one developer does most of the work and the other developer just zones out and gets distracted by something else, because you are constantly switching back and forth. Also, like two people who go on a diet together to help each other stick to it, one developer doesn't let the other developer get lazy and skimp out on the tests for a specific feature. That doesn't apply just to the test. As long as you have a pair of two experienced programmers, one of them it's going to let the other get away with writing a nasty piece of code. It's like a real-time code review.
My favorite aspect of the 3-2-1 experience was having the time to spend with other good software developers and just discuss the process of software development. TDD was a topic of conversation all week. Some believe in a strict adherence to TDD, "Test all the fucking time", if you will. Others believe TDD can be a warm blanket. It's easy to fall into a mentality that the tests pass, therefore the code works. Human testing can be a more effective process for discovering bugs in software, but human testing gets much less emphasis than TDD. I believe there is a lot of value in functional testing with tools like Selenium and I would like to see more emphasis on functional testing in the community. For my next project, I'm considering just specing the models and testing the rest of it with Selenium. If you are following the best practice of SCFM, you controllers should be mostly glue code anyway, so you get more out of testing from the browser via an automated, repeatable test than unit testing your controllers. It was a healthy debate and great to have a chance to be part of it.
I suggest all developers, whether you are an independent developer or a full-time employee for an organization, find an opportunity to participate in a 3-2-1. The amount of knowledge sharing that goes on is invaluable, exceeding what you get out of attending any conference. You spend the entire day pair programming with other top-notch developers and the evenings discussing all aspects of software development. This is the type of environment Evan Light envisions for DCamp. Developers working together, one-on-one, sharing knowledge and trading best practices.
Posted in
Technology |
Tags
Rails, Ruby |
4 Comments
11:33 PM EDT Sunday, July 13 2008
Just in case your having as hard a time keeping up as I am:
| Old and Busted |
The New Hotness |
| MRI |
Rubinius |
| Rails |
Merb |
| ActiveRecord |
DataMapper |
| Prototype/Scriptaculous |
jQuery |
| Plugins |
Gems |
| test/unit |
Rspec |
| ERB |
HAML |
| Ferret |
Sphinx |
| Apache |
Nginx |
| Mongrel |
Thin |
| Capistrano |
Vlad |
| SVN |
Git |
| Twitter |
FriendFeed |
Did I leave anything out?
Posted in
Technology |
Tags
Ruby |
4 Comments