Ruby Programming/Unit testing

Unit testing is a great way to catch errors early in the development process, if you dedicate time to writing appropriate and useful tests. As in other languages, Ruby provides a framework in its standard library for setting up, organizing, and running tests called Test::Unit.

There are other very popular testing frameworks, rspec and cucumber come to mind.

Specifically, Test::Unit provides three basic functionalities:


 * 1) A way to define basic pass/fail tests.
 * 2) A way to gather related tests together and run them as a group.
 * 3) Tools for running single tests or whole groups of tests.

A Simple Introduction
First create a new class.

Let's start with an example to test the SimpleNumber class.

Which produces

So what happened here? We defined a class TestSimpleNumber which inherited from Test::Unit::TestCase. In TestSimpleNumber we defined a member function called test_simple. That member function contains a number of simple assertions which exercise my class. When we run that class (note I haven't put any sort of wrapper code around it -- it's just a class definition), the tests are automatically run, and we're informed that we've run 1 test and 2 assertions.

Let's try a more complicated example.

Now there are three tests (three member functions) in the class. The function test_typecheck uses assert_raise to check for an exception. The function test_failure is set up to fail, which the Ruby output happily points out, not only telling us which test failed, but how it failed (expected <3> but was <4>). On this assertion, we've also added an final parameters which is a custom error message. It's strictly optional but can be helpful for debugging. All of the assertions include their own error messages which are usually sufficient for simple debugging.

Available Assertions
Test::Unit provides a rich set of assertions, which are documented thoroughly at Ruby-Doc. Here's a brief synopsis (assertions and their negative are grouped together. The text description is usually for the first one listed -- the names should make some logical sense):

Structuring and Organizing Tests
Tests for a particular unit of code are grouped together into a test case, which is a subclass of Test::Unit::TestCase. Assertions are gathered in tests, member functions for the test case whose names start with test_. When the test case is executed or required, Test::Unit will iterate through all of the tests (finding all of the member functions which start with test_ using reflection) in the test case, and provide the appropriate feedback.

Test case classes can be gathered together into test suites which are Ruby files which require other test cases:

In this way, related test cases can be naturally grouped. Further, test suites can contain other test suites, allowing the construction of a hierarchy of tests.

This structure provides relatively fine-grained control over testing. Individual tests can be run from a test case (see below), a full test case can be run stand-alone, a test suite containing multiple cases can be run, or a suite of suites can run, spanning many test cases.

Naming Conventions
The author of Test::Unit, Nathaniel Talbott, suggests starting the names of test cases with tc_ and the names of test suites with ts_

Running Specific Tests
It's possible to run just one (or more) tests out of a full test case:

It is also possible to run all tests whose names match a given pattern:

Setup and Teardown
There are many cases where a small bit of code needs to be run before and/or after each test. Test::Unit provides the setup and teardown member functions, which are run before and after every test (member function).

Radius of a bike tire
Implement a class with a public method that can solve the following problem: A user has a rubber bicycle tire of an arbitrary circumference. When one side is cut, and the bike tire is stretched into a line, it is measured at an arbitrary length. From this length, determine the radius of the bike tire originally.

After this, write a test case to test the class. It should include at least 2 methods to test.