The Pragmatic Studio

Ruby Learning Test #1: Are You There, World?

March 18, 2005

How I Learned Ruby

A couple years back I flipped open the first edition of Dave and Andy’s wonderful book Programming Ruby. A few pages in I’m introduced to my first Ruby code. It looks somewhat foreign… maybe it’s just my strained eyes. Ah, it’s so clean and elegant! I want to try it. Oh, but will I have the time and opportunity to keep learning Ruby? Just when I’m at the tipping point, Dave beckons me closer: "Go ahead, run the code and you’ll see the following output." Those words are intoxicating to a programmer, and so I proceeded to write my first Ruby program.

But it was at that very moment when I asked Ruby a question and it responded with the answer that I learned more than Ruby. I knew right then that I didn’t want to run the example just once. No, I wanted to preserve the example and ultimately build a Ruby knowledge base that I could draw from later. That meant the examples had to be executable and check their own results. That sounded familiar. I needed to write tests.

There was just one problem: I didn’t yet know enough about Ruby to write a test. Oh, I could print stuff to the screen, but that sort of manual inspection breaks down quickly. Using JUnit had taught me the value of writing automated tests that put the computer in charge of remembering what outcome is expected. Thankfully, when you install Ruby you automatically get the Test::Unit unit testing module. And so I had to learn enough about Test::Unit to learn Ruby itself. The result, years later, is the following: </p>

[~/work/code/ruby/learn] $ ruby all_learning_tests.rb
ruby all_learning_tests.rb
Loaded suite all_learning_tests
Started
.......................... (a lot more dots)
Finished in 1.467719 seconds.

215 tests, 452 assertions, 0 failures, 0 errors

That’s pretty much everything I know about Ruby, give or take. It’s a living repository of knowledge; the test suite grows each time I learn something new. Promptly after installing a new version of Ruby, I run the test suite. I do that not necessarily because I think the tests will find a bug, but rather to get a heads-up when something has changed and I need to reset my expectations. It’s also a good way to identify when something has been deprecated.

But the real value of writing these tests was less about testing, and more about learning. Through trial and error they taught me how Ruby and its rich set of libraries really work. Not surprisingly, typing in code and running it makes you remember. Indeed, writing learning tests is a fun way to poke and prod any new language or API. And with every test you write you’re investing in an executable knowledge base.

So You Want To Learn Ruby?

Perhaps you’ve been meaning to learn Ruby for fun or profit, but you just don’t know where to start. I’d like to help by trying a bit of an experiment. No, I’m not going to send you a copy of my Ruby learning tests. The learning comes through doing.

Rather, I’ll start by showing you how I wrote my first Ruby learning test. Then, over the coming weeks and months, I’ll spoon-feed you more tests as a starting point for exploring new facets of Ruby.

After we get the fundamentals down, I won’t try to explain in depth what the Ruby code in each test does. If the test is written well, you won’t need an explanation. And frankly, you really do need a copy of Programming Ruby for a definitive guide and tutorial to Ruby. (Disclaimer: I don’t get royalties from sales of the book, but it’s quite excellent and it saves me typing here.) When I introduce a major new topic, I’ll try to call out page numbers from that book. Then you can go to those pages, read up on how and why something works, and codify your understanding with learning tests.

One caution before we go any further: Ruby has spoiled many programmers.

Write a Learning Test

Starting is always the difficult part, so let’s get right to it. Download and install Ruby using the one-click Windows installer, tarball, or any other option.

Now we get to write our first learning test. Strings are always fun and easy pickings, plus they usually have a lot of interesting methods to play with. So change to a directory where your learning tests will live (mine’s called learn) and create a file called string_test.rb that contains the following Ruby code:

require 'test/unit'

class StringTest < Test::Unit::TestCase

  def test_length
    s = "Hello, World!"
    assert_equal(13, s.length)
  end

end

You just wrote your first Ruby class! The require statement at the top of the file loads the Test::Unit module: Ruby’s built-in unit testing framework. The next line declares a class called StringTest. The < symbol on the class declaration line means that StringTest is a subclass of Test::Unit::TestCase. Classes that are test cases must extend Test::Unit::TestCase to enjoy the set of computer-checked assertion methods that we’ll use later.

The StringTest class has one method: test_length. Test methods take no parameters and they must be named with a "test" prefix so that Test::Unit knows that they’re tests we want to run. After defining the method, we create a variable called s that references an object of class String. We don’t have to declare the type of the variable because Ruby figures out its type based on what the variable can do. (More on this exciting topic later.) We then call the assert_equal method (inherited from TestCase) with two parameters. What we’re saying here is we expect the length of the string s to be 13. Then the method definition ends with the end keyword, as does the class definition.

Now, let’s run the test.

$ ruby string_test.rb
Loaded suite string_test
Started
.
Finished in 0.0051 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

OK, so we can count! Let’s kick it up a notch. Double-quoted strings can also contain any Ruby expression of the form #{expression}. So let’s add another test.

def test_expression_substitution
  assert_equal("", "#{'Hello! ' * 3}")
end

Hmm, that’s not going to pass. We’re asserting that the result of the expression passed as the second parameter will equal an empty string. We may not be sure what the expression will do, but we’re pretty sure it will evaluate to a non-empty string. So why use "" as the expected value?

Sometimes when we’re trying to learn something new, we just don’t know the answer. We might have a guess, but if we trust the source then we can do one better: we can ask. By leaving the expected outcome empty, we’re asking Ruby to tell us what the answer is when we run the test.

$ ruby string_test.rb
Loaded suite string_test
Started
F.
Finished in 0.093141 seconds.

  1) Failure:
test_expression_substitution(StringTest) [string_test.rb:13]:
<""> expected but was
<"Hello! Hello! Hello! ">.

2 tests, 2 assertions, 1 failures, 0 errors

Ah, so that’s how it works! Thanks, Ruby! It’s pretty much what we expected: Print the phrase "Hello!" three times. Now we plug that answer in, happy to have learned something new.

def test_expression_substitution
  assert_equal("Hello! Hello! Hello! ", "#{'Hello! ' * 3}")
end

With the answer recorded, we can re-run just that test method.

$ ruby string_test.rb --name test_expression_substitution
Loaded suite string_test
Started
.
Finished in 0.005031 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Wait just a doggone minute! What did we just do? We ran a test, knowing it would fail, and then we picked out the answer from the error and plugged it into the test so that it now passes. You wouldn’t dare try this with code you were really testing!

We have no idea if Ruby did what it should have done: we just know what it did. That is, we used the language as a tool to explore itself. In the same way that a test is better than a specification, the language is better than a description of the language. The test is definitive—when we ask Ruby what the answer to 'Hello! ' * 3 is, we’re going to the horse’s mouth. It doesn’t matter what the documentation says; what we’re testing is what actually happens. And that’s learning. So the test is both a learning test and a regression test.

Now It’s Your Turn

We’ve barely scratched the surface of what the Ruby String class can do. See pages 61 and 606 of the book for a full listing of all the methods and explanations for each. You can also get a list of all the messages that String responds to by typing:

> ri String

Indeed, there’s a lot to learn, but these first tests give you a framework to explore the many wonders of strings. Better yet, you have a safe learning environment and a language that makes it easy to write and run tests. So go ahead, write a few more string tests, then run them and Ruby will tell you the answers. Give gsub and squeeze a poke. It’s just a start, but it may be more Ruby code than you’ve written before.

We’d love to have you join us in our popular Ruby video course! This course takes a project-based approach, teaching you how to write a complete Ruby program from scratch using all the strengths of Ruby. You’ll come away with a solid understanding of the fundamentals of Ruby, and how to put all the pieces together, so you can confidently write your own Ruby programs or be more productive using Rails!

Ruby Course