Monday, January 08, 2007

RSpec and Unit Tests: Localizing Problems or Reducing Coupling

I'm just starting to work through a copy of Working Effectively with Legacy Code by Michael Feathers. I've had my eye on the book for a while, and am really glad I was able to borrow a copy (now I just need to buy my own). Like any good book it's making me think, making me question things I've taken at face value. So far, the best example of this is in his discussion of Unit Testing.

I've recently been looking more at RSpec and BDD instead of Test::Unit, bu haven't quite caught the vision. Something that Michael wrote made me wonder about it even more. One of the big values of RSpec is that it reduces coupling between tests and code, which sounds really good. Then I read Michael's list of the qualities of good unit tests:

  1. They run fast.
  2. They help us localize problems.
(see p. 13).

At first this seems like it contradicts the aim of reducing coupling (a la RSpec). I can see two possible responses:

  1. RSpec isn't about unit testing, it's about testing at a higher level — but does this mean we should still have unit tests?
  2. Specifications and Contexts should be written against specific classes (though not necessarily against specific methods — but doesn't this limit the decoupling we can do?

I'm not sure a completely grok this. Anyone want to weigh in?

3 comments:

James said...

I'll take a stab at your first question (re: unit tests). Bear in mind that I'm speaking in terms of my understanding and vision of both RSpec and Unit Tests.

RSpec is sort of like writing a contract: you define what to expect, and what not to expect, from a given model under general cases. Unit tests, on the other hand, probe specific cases or instances where behavior can go awry. In this light, I see both RSpec and Unit Tests having a role in my development process.

pate said...

James,
that fits pretty well with the conversation Kevin Tew and I had shortly after I posted this. (I'll be posting a follow-up based on that conversation in the next day or two.)

Anonymous said...

Can I make a non-philosophical plug for RSpec? I just like the way it stays out of your way. The Contect/Specify thing is a mini-DSL. In xUnit and Test::Unit I was always trying to invent class names and method names to indicate what I wanted to test. I was always unclear where the scope started and ended. NUnit hd a nice little [TestFixture] thing you could use to scope things with.

RSpec is just beautiful here. No methods to invent, and the assert thing just goes away. Doesn't this read better:
context "Given a MyModel updated from a form" do
specify "it validates its date field correctly" do

model = MyModel.new({:date => '1/1/2007'})
model should_be_valid
end
end

After my specs pass, I can run a report documenting my tests. It integrates nicely with Rails too. I can test controllers indepedent of their views and vice-versa. I can test things independent of the back-end DB as well. Also, I can get my test speed to be just about a little more than a second for all my tests. It also works well with rcov and Selenium.