![]() |
Articles Feed |
Categories
Archives
- July 2010 (5)
- June 2010 (4)
- April 2010 (3)
- March 2010 (2)
- February 2010 (2)
- January 2010 (1)
- December 2009 (1)
- October 2009 (2)
- September 2009 (2)
- August 2009 (1)
- July 2009 (5)
- June 2009 (2)
- May 2009 (2)
- April 2009 (8)
- March 2009 (7)
- January 2009 (2)
- December 2008 (3)
- November 2008 (5)
- October 2008 (4)
- September 2008 (6)
- August 2008 (4)
- July 2008 (5)
- June 2008 (5)
- May 2008 (4)
- April 2008 (2)
- February 2008 (4)
- January 2008 (2)
- December 2007 (2)
- November 2007 (2)
- October 2007 (2)
- September 2007 (1)
- August 2007 (3)
- July 2007 (1)
- June 2007 (4)
- May 2007 (7)
- April 2007 (2)
- February 2007 (3)
- January 2007 (3)
- November 2006 (3)
- October 2006 (3)
- September 2006 (17)
- November 2004 (1)
Test Driven Debugging
by: eric | October 11th, 2007 | 0 comments »
I hate debugging. Loathe it, despise it, pick your synonym and I’ll use it. Computer programming is building something out of nothing, making the computer do things it didn’t know how to do, until I told it. I absolutely love code refactoring, moving code around, organizing it better, cleaning it up and watching tests pass. But stepping through code looking for the place where I accidentally typed quesiton instead of question? Shoot me in the head. In addition to the frustration of debugging, a bug can only be explained by one thing: I screwed up. I hate screwing up even more than I hate debugging. My hatred of debugging is one of the reasons I started practicing TDD. After all Test Driven Design means I never debug right?
Of course we all debug some of the time, usually as a deadline hangs over our heads. This tends to be where practices break down. You hack and slash, desperately trying to make the program JUST WORK only to watch it fail and fail again. Eventually you get the bug fixed, probably with an ugly hack right before (or right after) a customer demo. I won’t claim that “Test Driven Debugging” is a solution, but it certainly helps me in times of need.
Let me take an example from real life. As our last iteration ended I was on a roll. I had a TODO list of items and everything kept clicking into place. I’d implement a feature, and it work the way I wanted to the first time. There was just one last part of the story, moving the items on the screen. I wrote a test, it passed. I wrote another, made it pass. I got everything working the way I wanted too - then tried the actual application, and it went boom. Moving just wasn’t working the way I expected. Items would jump around the screen seemingly at random. I realized what the bug was - items retrieved from the database were not coming back in the right order. So how do I go about fixing it?
Well I don’t always practice what I preach, so I started hacking and slashing. I threw order_by and find statements around, and even wrote a method that wasn’t tested in a desperate effort to make the demo. Of course I didn’t make the demo, and then sat down and calmly did what I should have done. I wrote some tests. Well first I wept quietly, but that’s none of your business.
In our application there’s an ActiveRecord called Design, and it has Widgets which use acts_as_list to keep track of themselves within the Design with a position. Don’t worry about the details if you aren’t familiar with Ruby on Rails, the important thing is the steps. First I wrote a test in the Design.
it "should order widgets by position" do widget1 = Admin::Widget.create(:design => @design) widget2 = Admin::Widget.create(:design => @design) widget3 = Admin::Widget.create(:design => @design) widget3.insert_at(0) widget3.save @design.reload @design.widgets[0].id.should eql(widget3.id) @design.widgets[1].id.should eql(widget1.id) @design.widgets[2].id.should eql(widget2.id) widget3.position.should eql(0) widget1.position.should eql(1) widget2.position.should eql(2) end
Hmmm… it passed. That’s interesting. It’s coming back correct from the model. I guess it’s time to write a test in the controller:
it "should have the widgets ordered by position - requires real data" do question = Admin::Question.create(:name => "question") design = Admin::Design.create widget_pos2 = Admin::Widget.create(:question => question, :design => design) widget_pos1 = Admin::Widget.create(:question => question, :design => design) widget_pos2.position.should eql(1) widget_pos1.position.should eql(2) widget_pos2.insert_at 2 widget_pos2.save! widget_pos1.reload widget_pos2.reload widget_pos2.position.should eql(2) widget_pos1.position.should eql(1) get :edit, :id => design.id assigns[:design].should be_instance_of(Admin::Design) assigns[:design].widgets[0].position.should eql(1) assigns[:design].widgets[1].position.should eql(2) end
There’s a couple things worth noting in this test. I used real data instead of mocks, although RSpec style would typically use mocks. The reason I didn’t use mocks is that while they are great at decoupling data just like this, in the case of a bug I could very well pass my mock, only to discover that I’ve been calling the wrong methods all the time. I want the real data here. Some would argue this doesn’t make it a Unit Test, and I’d be hard pressed to disagree, but it still is a valuable test. The second is that I’m doing should eql before I call get, which is the method under test. The reason for that is I want to test my assumptions. Often times when you have a bug it’s because you made a mistake in your assumptions, such as thinking the widgets were always being returned in position order, and I want to tease out any issues in mine. The last thing I need is a test that passes by coincidence.
So I ran the test - and it passed! Then I started the application again, and it worked! Did tests have a magical power that fixed the bug? Of course not. I had fixed the bug during my hacking and slashing, but I was frantic so I hadn’t calmly restarted my server and had a passing test. Had I done this from the get go I would have recognized the bug ( which was using an explicit design.widgets.find(:all) instead of design.widgets, thereby retrieving the data from the DB in id order ) and likely passed the demo. Once again lesson learned, sometimes I have to learn more than once. I’ll clean up the test above to remove the now unneeded extra checking, but I’ll leave it in the code, so that this bug never shows itself again. Thou art commanded - never ever reintroduce a bug. Now go forth and program my son.
Micah's General Guidelines on Ruby require
by: micah | October 8th, 2007 | 14 comments »
Ruby files have to require other files. There’s not avoiding it. Techniques to manage require statements are numerous and varied. Having tried most of them, I’ve found a system that works well for me. What follows are the guidelines I use to manage Ruby require statements.
1. Establish a Convenient Search Path
Although it’s possible to use absolute paths or complex relative paths such as below,
- require "/Users/micahmartin/Projects/ttt/lib/game"
- require File.dirname(__FILE__) + "../../../lib/ai/winner"
this should be avoided as much as possible. Otherwise you’ll be fixing dozens of require statements every time you move a file. It’s no fun. You’ll want your requires to look like this:
- require "game"
- require "ai/winner"
To achieve this, add your lib directory to the ruby search path.
$: << File.expand_path(File.dirname(FILE) + “/../lib”))
This will have to go in one of the first files that gets loaded. If you’ve got a standalone Ruby app, you could add this to the startup script. In a Rails app, it can go in environment.rb. If you’re using RSpec, you may want to add it to spec_helper.rb.
2. Independent Requiring over Require Farms
In some projects, you’ll find files that contain nothing but require statements. It’s common in gem projects. The advantage is that users of the library need only require one file which in turn requires everything else you’ll need. Convenient huh? Sure is. But there are 2 major consequences of this:
- Maintenance Mayhem - Every time you add, delete, or rename a file you have to remember to update the require farm file. It’s easy to forget. And the order of requires can get very hard to manage especially if you end up with cyclic dependancies.
- Drowning in Dependancies - Require Farms have a tendency to require more than you want. If there’s only a portion of the library you’d like to use, with the Require Farm you get the whole thing. The extra dependancies will consume more memory at run time and they may add undesired behavior to your system. In general, this is a violation of the Dependancy Inversion Principle.
Rather than build Require Farms, allow each file to be responsible for it’s own dependancies. Let each file require the files it needs to work. This approach can be a bit annoying as you’ll find your self using altogether more require statements, but it’ll pay off in the long run. With each file independently managing it’s dependancies, the system will be easier to maintain and it’s components more reusable.
3. Building Absolute Paths
Try as you may, you can’t always avoid using absolute file paths in your require statements. The following is reliable way to refer to other files.
- File.join(File.expand_path(File.dirname(__FILE__)), "..", "spec_helper")
Let me break it down. First:
- File.dirname(__FILE__)
This gives you the directory of the current file that is being executed. However, you never know what you’re gonna get since the form of the path is based on how the program was executed. This might give you an absolute path or it might give you a relative path from anywhere on the system. In some cases, Ruby won’t do what you expect with relative paths so it’s best to expand this into an absolute path.
- File.expand_path(File.dirname(__FILE__)
Now you’ve got an absolute file path and you just have to add a relative path to the desired file. Normally I’d do this:
- File.expand_path(File.dirname(__FILE__) + "../spec_helper")
However, this is not quite portable since I’m using forward slashes. To get this to work on any system independent of file separator, use File.join as shown above. Use this technique to require files when you don’t have your search path configured.
Happy Requiring!
#