Another bonehead revelation
December 8th, 2007
Ok, so I’m an idiot..
I’ve tried more than a few times to get a local version of RDoc with no luck. Sure I could generate the HTML, but the classes were missing very critical methods. Like String#split.
Last night at Borders, I had an idea. What if I tried generating the docs from the source, rather than the installation? ‘Lo and behold, that was exactly it. Gah, chalk another one up for me.
Hopefully I can garner a little respect by pointing others in the right direction who can’t figure it out, and are as embarrassed as I was about asking for help.
Steps to recreate docs
- Download the ruby source code
- Uncompress it and cd into the directory
- Execute
rdoc --all -o ~/.rdoc
It’ll take a little while to generate, but you’ll have your local version.
A couple perspectives on testing common scenarios
December 6th, 2007
One Perspective
Suppose we have a Rails ActiveRecord model for a book:
class Book < ActiveRecord::Base
validates_presence_of :title
end
Some people suggest testing the validation of the title by just checking to see if either A) validates_presence_of(:title) was called or B) whatever the side effect of validates_presence_of(:title) is present in the object (e.g. creating a method, adding an object to an array of validators, etc.) I believe their objective is to both DRY up their tests as well as not worrying about testing validates_presence_of, since that should already be tested by the framework.
I tend to disagree with this approach, and would much rather we test the behavior of a book. Let’s instantiate a book without a title, and assert that it is in fact invalid.
# for test/unit
def test_books_should_require_titles
book = Book.new(:title => nil)
assert !book.valid?
assert book.errors.on(:title)
end
# for rspec
describe Book do
it "should require titles" do
book = Book.new(:title => nil)
book.should have(1).error_on(:title)
end
end
While I do think the other approach is better than not testing at all, I believe testing the behavior in this case has a higher expected value. In other words, I feel that divorcing the test from the particular implementation will make our test more robust than the former. The prime example I can see for this scenario is that testing the behavior will encompass both using validates_presence_of and any other form (in case our implementation is a bit more complex).
I can respect trying to DRY up the tests and not wanting to duplicate it for every attribute. For that I would suggest creating a helper that tests a series of attributes for you. Something like:
describe Book do
should_require(:title, :author, :published_at, :copyrighted_at)
end
Backpedaling a little
I actually use the first approach often when I’m testing my controllers. Usually when I want to focus on a testing an action in my controller, and I want to test it in isolation. However I do want to assert that a filter is assigned to that controller.
Suppose we had a controller for library on a college campus that only allowed Students and Faculty in:
class LibraryController < ApplicationController
before_filter :requires_students_or_faculty
def checkout
... implementation of the action ...
end
end
First I would assert that the library checks people, but I don’t want to worry about it when I want to test checkout
# Staying with RSpec
describe LibraryController, "filters" do
should_have_before_filter(:requires_students_or_faculty)
end
describe LibraryController, "something to do with checkout" do
stub_all_filters!
it "should redirect the patron outside after checking out a book" do
... implementation of the test ...
end
end
Minor Note – stub_all_filters! runs through all filters and stubs them out so they all return true, making them irrelevant.
I’m not doing this in order to remove the responsibility of testing the the filter itself. I whole heartedly believe it should be tested too, but separating the tests can help avoid clunky tests with a lot of noise.
Another way to look at it, is to consider this perspective of “behavior”. While I do agree that the filters contribute to the overall behavior of a controller, when I want to test a specific behavior (in this case, checkout), I don’t want my tests to have to satisfy preconditions requires_students_or_faculty. For example, checking if a user is a student or faculty could be complex, and there’s no reason to care about that when testing checkout
Testing and Tautologies
December 4th, 2007
A question that I’m asked frequently, especially in regards to mock testing is: “I know it seems worth it to test, but isn’t it just a tautology? I mean when your code changes you have to change your tests.”
I have a couple responses to this. It is true that there are times when the code changes, that I have to update the tests too, however that’s not always true, even with mock testing. I’ve found the more I use mocks, the more I end up refactoring and decomposing code into simpler bits. Tests can be one of the loudest critics of your code, if you know how to listen.
Tautologies is an interesting topic. At the last XP West Michigan Scott Miller mentioned that a fellow coworker of his Bill Bereza brought up the idea of double entry bookkeeping in accounting. And that when it applied to software development, he really didn’t have a problem with it. I never thought about testing in that light. Whenever I would try to answer the question before, though I would fail to find an adequate reason why testing was not just like double entry bookkeeping. But in reality I’ve just never encountered a problem with it.
To put it simply, even if it’s considered double entry coding, I don’t notice a productivity loss, and in fact I’ve noticed gains. Though I know that can be unsatisfying for people who want more than just one developer’s opinion on his personal experiences.
Regardless of the existence of tautologies, tests allow you to mark off small chunks of executable code. One of my biggest irritations is to constantly open up the browser and click around to ensure what I just wrote works during development. In the end, I will go through the manual steps to wrap something up, however problem solving quickly loses its luster when it requires 5-10 steps every change. It gets exciting when you implement a chunk of functionality only to look over and realize that your mongrel server was down.
Similarly, when implementing a piece of code that depends upon another. It is such a breath of fresh air to finish the former, without even worrying about the existence of the latter. I would hate the rabbit holes I would travel in order to finish my original thought.
So from the outside, some of the tests may appear to be duplicates of their implementation, though I would contend that it is merely a different perspective of correctness. But weighing the good with the bad, tests take me much further faster than I would otherwise go.