Posted by admin on Apr 4, 2011 in
development,
tdd
Estimation is hard. It may not be listed as one of the top two problems in computer science, but it’s at least a close third. Over the years I’ve gotten to try all sorts of methodologies for estimating. Waterfall, Agile, Points, Stories, Requirements, Features, Epics, Pomodoros, Billable hours… you can go on forever with the Jargon of Management, trying to get information from a developer about how much longer The Client has to wait until The Feature is finished.
Rubik’s Cubes
About 4 years ago I started a job where a coworker was an avid Rubik’s cube solver. I learned the steps and began competing with him to see who could solve a cube the fastest. Eventually, my top time ended up around a minute and thirty seconds. There are guys who can do it much faster–but I’m interested in consistency and improved speed. Sometimes I can solve very quickly because some steps can be skipped (i.e. they are done for you) or because I’m using a very good cube, but in general, I can solve a cube in 1:30 – 3:00.
But I don’t do it blindfolded.
Blindfolded
The world record for the fastest solve is just under 10 seconds, but the world record for the fastest blindfold solve (i.e. from the time you pick it up, look it over, and then blindfold yourself to begin solving) is over a minute and a half–10 times as long. The reason is simple–feedback. Immediate and quick feedback allow you to take shortcuts and spend less time worrying about the next 200 moves and just worry about the next step or two.
So why do we still tend toward “blindfolded” approaches in software?
Waterfall vs. a Feedback loop
Call it Agile, call it scrum, call it feedback–it doesn’t matter. What makes a team think they’re going to deliver better because they took the time to figure out all the steps, when you don’t know exactly how one step will affect the next? It’s foolish when you consider it, but still, so many teams do it. Even the supposed “agile” teams end up blindfolding releases and then trying to ‘feedback’ the sprints. That’s not to say there should be no planning, but getting customer and developer feedback is crucial to accurate and fast solutions to problems.
What should we be doing instead? In Rubik’s Cube solutions, you typically identify the next ‘step’ and iterate over it, examining the result and deciding the following step at that point. You might be able to ‘blindfold’ for each step, but you’d still need to look at the end to figure out where to go next. I think a similar paradigm works in software building. Identify the key feature, and let the developer work on it, deploy it, and get feedback. Iterate on it again if it’s not quite right. If it is, then move along to the next step or feature.
Analogy Breakdown
Where does this analogy break down? Architectural changes. You can’t make sweeping changes underneath the system without a good deal of planning and “blindfolding” where you step through, unsure of the effect on the system because you can’t actually evaluate it. There are times this must occur–but as developers, we probably tend toward a desire to rearchitect more often than we should. Always consider what the right solution is and evaluate the cost of technical debt vs. the potential cost of an underlying change that could have pervasive and unknown effects. Sometimes it is necessary, but generally it’s not. YAGNI is your friend.
Takeaway
The tighter the feedback loop, the more accurate and more quickly you’re able to deliver a solution, whether in Rubik’s world or in the Software space. Focus on tight loops with customer feedback and developer feedback and watch your estimation troubles diminish.
Posted by admin on Dec 3, 2010 in
tdd,
test,
Uncategorized
We are impatient people. This is something that we must work to fix in order to grow as individuals, but it is something that serves the automator well–or can be our downfall.
Joel Splosky wrote on the “Joel Test” that having anything less than the best tools money can buy is rediculous for a development team. The reasoning is this: If you’re paying developers what they are worth, then they are expensive, and wasting their time while they’re reading the Onion waiting for a build will kill your productivity–and your bottom line.
The same goes now for Test Driven Development. Having a great test suite is nearly essential, and anything more than a trivial application will have difficult and long running tests, but these tests must be managed well and there must be a way for a developer to quickly run through a cross section of the test suite as a sanity check before checking in. If your tests take 1 minute to run, then the developer is 6 times less likely to run them than if they take 10 seconds to run. If your tests take 30 minutes to run, then the developer is 60 times less likely to run them than if they take 30 seconds. Every additional test is great for coverage, but if it adds time, there is a point of diminishing returns.
Tagged tests are a must, and a solid set of fast tests that touch much of the code base is essential. It must also be easy to run individual tests–anything that hampers this reduces the liklihood of continued test driven development. Testing becomes a chore again and running the tests becomes an excuse for swordfighting in the hall.
So take the time to improve the speed of anything you wait on as developers–and reap the benefits tenfold.
Posted by admin on Mar 3, 2010 in
rails,
ruby,
tdd
I’ve long put off testing my controllers because of user authentication and nested controllers, dealing with stubs, etc.
But today, a fully working test!
As background, Advertisers have many trackers and the routes look like this:
1 2 3 4 5 6
| # config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :advertisers do |advertisers|
advertisers.resources :trackers
end
end |
To set everything up in the specs, I included all the files in the spec/support directory and used Mocha as my mock framework
1 2 3 4 5 6
| # spec/spec_helper.rb
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
Spec::Runner.configure do |config|
config.mock_with :mocha
end |
Then I set up my factories (rather than fixtures) using Factory Girl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| # spec/factories.rb
Factory.define :user do |user|
user.sequence(:login) { |n| "username#{n}" }
user.password 'password'
user.password_confirmation { |u| u.password }
user.sequence(:email) { |n| "email#{n}@example.com" }
user.first_name "Mama"
user.last_name "Foo"
end
Factory.define :advertiser do |advertiser|
advertiser.name 'Advertiser 1'
end
Factory.define :tracker do |tracker|
tracker.name 'Tracker 1'
end |
Now we get down to brass tacks. In order to make my tests DRY (appropriately) and allow for all my controllers to test if someone is logged in and has access, I set up this shared context
1 2 3 4 5 6 7
| # spec/support/user_authentication.rb
describe "an admin is logged in", :shared => true do
before(:each) do
controller.stubs( :login_required => true)
controller.stubs( :current_user => Factory.build(:user, :login => 'admin', :roles_list => ["super"]))
end
end |
From there, all we need to do is put it all together, setting up trackers parent @advertiser and the @tracker we’ll be using and stubbing the ActiveRecord find so that it always returns @advertiser
1 2 3 4 5 6 7 8 9 10
| # spec/controllers/trackers_controller.rb
describe TrackersController do
it_should_behave_like "an admin is logged in"
integrate_views
before(:each) do
@advertiser = Factory.create(:advertiser)
@tracker = @advertiser.trackers.create(Factory.attributes_for(:tracker))
Advertiser.stubs(:find => @advertiser)
end |
Now we simply specify the part of the path that is needed to find the nested route by using :advertiser_id => @advertiser
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| # spec/controllers/trackers_controller.rb
it "index action should render index template" do
get :index, :advertiser_id => @advertiser
response.should render_template(:index)
end
it "show action should render show template" do
get :show, :advertiser_id => @advertiser, :id => @tracker
response.should render_template(:show)
end
it "new action should render new template" do
get :new, :advertiser_id => @advertiser
response.should render_template(:new)
end
it "create action should render new template when model is invalid" do
Tracker.any_instance.stubs(:valid?).returns(false)
post :create, :advertiser_id => @advertiser
response.should render_template(:new)
end
it "create action should redirect when model is valid" do
Tracker.any_instance.stubs(:valid?).returns(true)
post :create, :advertiser_id => @advertiser
response.should redirect_to(advertiser_tracker_url(@advertiser, assigns[:tracker]))
end
it "edit action should render edit template" do
get :edit, :advertiser_id => @advertiser, :id => @tracker
response.should render_template(:edit)
end
it "update action should render edit template when model is invalid" do
Tracker.any_instance.stubs(:valid?).returns(false)
put :update, :advertiser_id => @advertiser, :id => @tracker
response.should render_template(:edit)
end
it "update action should redirect when model is valid" do
Tracker.any_instance.stubs(:valid?).returns(true)
put :update, :advertiser_id => @advertiser, :id => @tracker
response.should redirect_to(advertiser_tracker_url(@advertiser, assigns[:tracker]))
end
it "destroy action should destroy model and redirect to index action" do
delete :destroy, :advertiser_id => @advertiser, :id => @tracker
response.should redirect_to(advertiser_trackers_url(@advertiser))
Tracker.exists?(@tracker.id).should be_false
end
end |
Works! And works great! A minimal and excellent way to test your controllers, especially for access. You can easily create additional shared contexts with different user permissions and extend out the tests to make sure users that don’t have access can properly access them.