advertisement

Print

Cookin' with Ruby on Rails - Designing for Testability
Pages: 1, 2, 3, 4, 5, 6, 7

And then take another look...

recipe table after changing use_transaction_fixture
Figure 26

CB: There we go. Now that the recipes table is empty, let's run our category test again.

ruby test\unit\category_test.rb

rerunning category test
Figure 27

Paul: That's more like it. But, and don't take this wrong CB, but that's really not what I'd call a complete test. It seems to me like we need to check a couple more things. First, Rails is telling us it changed "beverages" to "beverage" but we haven't checked to see if there's actually a record in the table named "beverage." And we haven't checked to make sure that there's not a record named "beverages" any more.

CB: I knew you'd be good in the tester role, Paul ;-) You're right. Let's add those tests. So now our test method looks like:

def test_read_and_update
  rec_retrieved = Category.find_by_name("beverages")
  assert_not_nil rec_retrieved
  rec_retrieved.name = "beverage"
  assert rec_retrieved.save
  changed_rec = Category.find_by_name("beverage")
  assert_not_nil changed_rec
  unwanted_rec = Category.find_by_name("beverages")
  assert_nil unwanted_rec
end

So now when we run category_test.rb, we get

test results after adding new assertions
Figure 28

CB: So, we're sure that we can read and update records that are already in the table. Let's make sure we can create and delete records.

Paul: But haven't we already proved that with the fixtures setting up the table and the teardown method clearing it out?

CB: That's a good question, Paul. I think you can argue it both ways.  

On one hand, we've definitely shown with the read_and_update method that the database got loaded, otherwise there'd be nothing to read and update. And we took a peek at the table itself after running the recipe test and saw that the table had been emptied.  

On the other hand, there are a couple of things that make me, personally, choose to go ahead and write a couple more methods to test the create and delete capabilities explicitly. First, I look to my test cases to tell me what an app does. If I don't put in methods to test the create and delete functionality, then I have to remember that the app does that but that it's being tested some other way. I guess I'm getting to an age where I'm less apt to trust my memory ;-) Second, my unit tests are testing the Model and I want to see that happen. If you look at the fixture files, you don't see any explicit use of Models per se. So if I've got a lot of trust in my memory, and I trust that Rails itself is working properly, I could argue the additional tests aren't really necessary. But when I've got on my Tester hat, my SOP is "show me," not "trust me."

It's not much work and it's not going to add significantly to our test execution time. So, I'm going to go ahead and add the methods to category_test.rb. Actually, I think I'll combine them like we did for read and update.

def test_create_and_destroy
  initial_rec_count = Category.count
  new_rec = Category.new
  new_rec.save
  assert_equal(initial_rec_count + 1, Category.count)
  new_rec.destroy
  assert_equal(initial_rec_count, Category.count)
end

And then we rerun our test case, and...

category_test after adding create and destroy tests def 
Figure 29

CB: So now we're sure that, as it stands, our tests make us confident that our Category model is working as designed. Now let's do the same for our Recipe model. Open recipe_test.rb and replace the test_truth method with:

def test_read_and_update
  rec_retrieved = Recipe.find_by_title("pizza")
  assert_not_nil rec_retrieved
  rec_retrieved.title = "pie"
  assert rec_retrieved.save
  changed_rec = Recipe.find_by_title("pie")
  assert_not_nil changed_rec
  unwanted_rec = Recipe.find_by_title("pizza")
  assert_nil unwanted_rec
end

def test_create_and_destroy
  initial_rec_count = Recipe.count
  new_rec = Recipe.new
  new_rec.category_id = 1
  new_rec.save
  assert_equal(initial_rec_count + 1,Recipe.count)
  new_rec.destroy
  assert_equal(initial_rec_count, Recipe.count)
end

And now we run the recipe test case...

new recipe test case
Figure 30

And it looks like we've got the basics working there too. What do you think, Paul?

Paul: Pretty good so far, CB. Are we ready to start filling those holes we spotted?

CB: You bet. Let's use our tests to put a spotlight on them. The approach I'm hoping you'll help me introduce to Boss says it's best to put off writing application code until you have a failing test that demands it. Let's start with the Category model. In Rails, we use our Unit tests to make sure the Model is working properly. "Properly" means, at a minimum, the CRUD functionality that's at the core of pretty much all Rails apps. That's what we just wrote tests for. The next piece of "properly" means that the validations we need our application to do on the data being written to the database are working. And finally, we need to make sure that any methods we include in our Model are working.

We've decided that all our Category records have to have a name and that the length of the name can't be longer than 100 characters.  And we've already seen that we're not currently enforcing that rule. Even if I hadn't given the name field a default value of an empty string, the way it sits right now, a visitor could hit the space bar and effectively do the same thing. So I'd say we're probably going to need to use both validations and a method to check for visitors entering blanks for the name. But let's let the tests tell us what we need.

So let's add another method to our category test case to make sure we only save records that have a valid name. First we'll try to save a record with no name and expect that save to fail, then we'll try to save a record that does have a name and expect it to get saved.

def test_must_have_a_valid_name
  rec_with_no_name = Category.new
  assert_equal(false, rec_with_no_name.save)
  rec_with_name = Category.new(:name => "something new")
  assert rec_with_name.save
end

And now lets run it.

ruby test\unit\category_test.rb

testing for non-blank name
Figure 31

Pages: 1, 2, 3, 4, 5, 6, 7

Next Pagearrow