Posts Tagged ‘tdd’

How to slice up tests

May 7, 2008

One of the hardest things about test driven development is choosing how to slice up your tests. Do you do unit tests (trying to isolate one unit of code) or integrating tests (checking to see that it all works together to lead to some positive outcome)?

I guess you do both, but I still don’t have good instincts about what to do when. Zach Dennis responded to a post I sent to the rspec-users mailing list giving me some good advice about the test I wrote about here.

I’m starting to think my first test should really be to send an email and then see if it got there. Unfortunately, email takes minutes to deliver, and how long should the test suite wait? Maybe I should just give up on testing this component.

How to use mock objects

May 7, 2008

I’ve been really enjoying test driven development lately. It’s more fun, more relaxing, and I think more productive than the classic “just go for it” or “try to plan everything out ahead” styles of development.

For those who don’t know, “test driven development” (TDD) is a style of building stuff. Typically when you want to build something like software, you start off by planning what you’re going to do, or you just start building. TDD is different. You start off by creating an automatic test that tells you whether you succeeded or not. So if I wanted to write some code that added apples to the bin, I would start of writing something like this:

test "when you add apples to the bin, there should be more apples in the bin" do
  start_number = count_apples_in(the_bin)
  add_apples_to(the_bin)
  end_number = count_apples_in(the_bin)

  end_number.should_be_bigger_than(start_number)
end

Note that we haven’t actually written code that adds apples to the bin. What we did was write some code that can tell us if we have something that works. If we run the test, of course the test will fail. But now we have a goal, and we have a good feedback loop. So we write a little code, and see if the test passes. Write a little more, see if it passes. As soon as the test passes, a little green light lights up, and we’re like “yay! our code works!”

The benefits of this are many. First, it’s emotionally gratifying. It makes coding more fun because getting another test to run is never that far away. And if it is far away, you just write another test that’s halfway there. Second, it lets you write really big software and have some confidence that it all works OK together. Because as you write code you build up more and more tests, and you keep running all those old tests, you have a continuous sense of what is working.

Anyway, that was a bit of a digression. What I really wanted to write about is “mock objects”. Once you’ve written a few tests you start to realize that often you want to test a little bit of code that relies on other objects, but you don’t want to test those other objects together with this one. So, if A depends on B, which depends on C, which depends on D, you want to have four separate tests that test each of those things independently.

But how do you test them independently if they are dependent?

The answer is: mock objects. If you’re testing B, you create a “mock” of C, which is just a fake object that does what C is suppose to do in your particular test case. That way your test isn’t dependent on C working properly.

Here’s an example. I want my Mailer object to get set up properly. But the Mailer depends on the GitoriousConfig object, and I don’t want to test how that is getting set up. So really I just want GitoriousConfig to act right for the Mailer. So I use a mock:

  it "uses smtp server if config says so" do
    GitoriousConfig.should_receive(:has_key?).with('smtp_settings').and_return(true)
    GitoriousConfig.should_receive(:[]).with('smtp_settings').and_return({
      :address  => "smtp.postoffice.net",
    })
      
    Kernel.load("config/initializers/mailer.rb")   
    Mailer.smtp_settings[:address].should == "smtp.postoffice.net"
  end

What this code says is that GitoriousConfig should act as if it has a smtp_settings hash in it, and that the address entry points at smtp.postoffice.net. A depends on B, but I’m using a fake version of B so I can isolate the test to A’s behavior.

That allows me to keep my tests nice and clean and separate. Cool.

Debugging Rails Tests

November 16, 2006

I am just getting started with Ruby on Rails, an application framework that everyone seems to love for web applications. I am learning lots, so I figured I’d post things I learn here.

I’m doing “Test Driven Development” on this project, so instead of writing code first, I should write a test, right? So, I open test/unit/myclass_test.rb, add a test, save the file, and then run

rake test_units

which runs the tests. Of course, my code doesn’t work and I get an error, so now I want to debug my test. I want to see what went wrong. I search Google, and find a helpful presentation on debugging in Ruby. It turns out debugging the test is as easy as entering

ruby -rdebug test/unit/myclass_test.r

That drops me into the debugger. Now I want to add a breakpoint, so I type

help

which tells me that to add a breakpoint on line 8 of my file, I want this command:

break test/unit/myclass_test.r:8

I could also just type break 8, since I’m only debugging one file, but it’s useful to see how to use filenames. Now I’ve got a breakpoint so I can run ahead to it using the continue command:

cont

This gives me a bunch of errors, so I hit enter again, which means “repeat the last command” which happens to be “continue”.

I get more errors, so I keep hitting enter until it says

Breakpoint 1, test_load at test/unit/myclass_test.rb:8

Super! Now I’m here and I can poke around as I please. Again, the help command is your friend, but what I wanted to do is see what methods were available in the File class. So I used this command:

method File

and the debugger spits back

atime chmod chown ctime flock lstat mtime path truncate

Ahh! I was hoping to see a “basename” method, but it’s not there! Clearly the methods aren’t getting initialized the way I hoped. So, I’m off to do some more debugging.