Ruby Learning Test #2: Active Reading

(Fri Apr 08, 2005) [/Ruby#

I had no idea this test-driven learning approach would resonate with so many folks. Thanks for your kind words and encouragement!

What Happens Next?

I'm sorry to have made you wait so long for the second installment of this series. To be honest, I was waiting to get some input from you, dear reader, about how best to proceed. Although I've been using this technique to learn new languages and APIs for several years now, I've just started thinking about how to share it with others. You see, I don't want this to turn into another Ruby language reference. You probably already have Programming Ruby pressed against your bosom.

Since posting the first test, many of you have suggested great ideas about what you'd like to see covered in future tests. I'm delighted to hear that you're not expecting a language reference, and I won't need to hire a ghost writer. Instead, I'll be using this space to explore how to use the learning test technique to supplement other resources. That is, I'll show you how I wrote learning tests to internalize the core concepts I learned in the Ruby book. Things like classes, blocks, exceptions, modules, and threads. As well, please do keep the comments coming. Your input will influence how this plays out, and I'll be picking up the pace now that I know you're on the edge of your seat to learn more Ruby. So welcome aboard!

Pulled Up By Your Bootstraps

So you've installed Ruby and learned enough about the Test::Unit module to bootstrap some learning. It may not feel like much, but given this simple (and safe) testing environment, you can learn useful things about Ruby without having to learn much more about Test::Unit just yet.

Hopefully you felt motivated to write a few more tests to explore how String works---it has over 75 standard methods just waiting to serve you. You don't need a main() method or a debugger to try them out. Just a simple assert_equal will do, and you already know how that works. Do you need to write a test for each method right away? Certainly not. Instead, I wait until I learn about a new method, then I write a learning test in order to learn how the method really works.

Trying What You Read

So how does this technique work in practice? Last time we were focused on just feeling our way around writing tests and getting them to pass. We didn't exactly discuss how this changes the way you read the Ruby book.

Say, for example, you're reading through Chapter 2 of Programming Ruby, and Dave starts explaining how to send messages to objects. Then he shows you some code.

"Rick".index("c") --> 2

To the left of the arrow you see the Ruby expression. The resulting value of the expression is to the right of the arrow. To really learn how to program, I have to program alongside the book. Learning tests give me a way to incrementally try out and, well, test what I've learned. So when you see that format in the book, think of it as an assertion and write a test.

def test_index
  assert_equal(2, "Rick".index('c'))
end

Notice that the test method is named test_index. The test prefix is required by Test::Unit. The _ is the Ruby convention for separating words in method names. Finally, index describes what the test is testing.

I like to keep my test methods nice and short. They test just one thing, and I describe what they test using an expressive method name. That way, if I forget how the index method works, I can easily find an example in my learning test repository. In this case, a search would find the use of index in the assertion, regardless of the method name. But as you write more learning tests, expressive test method names become invaluable. I often find that if I'm unable to find a test quickly, it means the method name needs to be changed. So expect the names to get refined over time.

Coloring Outside the Lines

OK, so the learning test passes. Not surprisingly, the index method returns the index of the first occurrence of the given character. It's at this point in my reading that I'm generally not satisfied. I want to do a bit of exploration, and perhaps even try to break something. The learning test environment allows me to answer questions where the book doesn't.

What happens if the given character isn't in the string, for example? Knowing very little about the language at this point, you can make a guess and ask Ruby for the real answer.

def test_index_character_not_found
  assert_equal(-1, "Rick".index('t'))
end

(That test doesn't pass, by the way. Go ahead and run it to find out what the actual answer is.)

This is active reading. While you're reading, pay attention to things that pique your curiosity. If you keep reading, you risk losing that moment when your brain really wants to learn by doing. Writing a learning test gives you a way to immediately apply the material and, perhaps more importantly, seek answers to questions the material raises for you.

Your Next Mission

Use ri to find out what methods the Fixnum class responds to. Then create a test case in a file called number_test.rb and write a few tests. A Fixnum represents an integer value within a certain range. Here's a learning test demonstrating how to create one:

def test_create_fixnum
  f = 123
  assert_equal(Fixnum, f.class)
end

Notice the use of the class method to test the class of the object referenced by the f variable. If you create a number outside the range of a Fixnum, what is the class of the variable f?

Everything in Ruby is an object. Yes, even numbers. Try sending some messages to numbers. Here's a starter test:

def test_abs
  assert_equal(1, -1.abs)
end

What other tests can you think of for numbers? Chapter 5, Standard Types, of Programming Ruby shows example uses of numbers and the reference library in the back documents all of the methods. As you read about numbers in the book, what questions do you have? Answer the questions by writing learning tests. I'm holding back writing a bunch of assert_equal tests here because that won't help you learn. But next time we'll dig into how to write learning tests for facets of Ruby that require more than a simple assertion of an expression's value.