Writing Unit Tests






Writing Unit Tests

Credit: Steve Arneil

Problem

You want to write some unit tests for your software, to guarantee its correctness now and in the future.

Solution

Use Test::Unit, the Ruby unit testing framework, from the Ruby standard library.

Consider a simple class for storing the name of a person. The Person class shown below stores a first name, a last name, and an age: a person's full name is available as a computed value. This code might go into a Ruby script called app/person.rb:

	# app/person.rb
	class Person
	  attr_accessor :first_name, :last_name, :age

	  def initialize(first_name, last_name, age)
	    raise ArgumentError, "Invalid age: #{age}" unless age > 0
	    @first_name, @last_name, @age = first_name, last_name, age
	  end

	  def full_name
	    first_name + ' ' + last_name
	  end
	end

Now, let's write some unit tests for this class. By convention, these would go into the file test/person_test.rb.

First, require the Person class itself and the Test::Unit framework:

	# test/person_test.rb
	require File.join(File.dirname(__FILE__), '..', 'app', 'person')
	require 'test/unit'

Next, extend the framework class Test::Unit::TestCase with a class to contain the actual tests. Each test should be written as a method of the test class, and each test method should begin with the prefix test. Each test should make one or more assertions: statements about the code which must be true for the code to be correct. Below are three test methods, each making one assertion:

	class PersonTest < Test::Unit::TestCase
	  def test_first_name
	    person = Person.new('Nathaniel', 'Talbott', 25)
	    assert_equal 'Nathaniel', person.first_name
	  end

	  def test_last_name
	    person = Person.new('Nathaniel', 'Talbott', 25)
	    assert_equal 'Talbott', person.last_name
	  end

	  def test_full_name
	    person = Person.new('Nathaniel', 'Talbott', 25)
	    assert_equal 'Nathaniel Talbott', person.full_name
	  end

	  def test_age person =
	    Person.new('Nathaniel', 'Talbott', 25)
	    assert_equal 25, person.age
	    assert_raise(ArgumentError) { Person.new('Nathaniel', 'Talbott', -4) }
	    assert_raise(ArgumentError) { Person.new('Nathaniel', 'Talbott', 'four') }
	  end
	end

This code is somewhat redundant; see below for a way to fix that issue. For now, let's run our four tests, by running person_test.rb as a script:

	$ ruby test/person_test.rb
	Loaded suite test/person_test
	Started
	….
	Finished in 0.008837 seconds.

	4 tests, 6 assertions, 0 failures, 0 errors

Great! All the tests passed.

Discussion

The PersonTest class defined above works, but it's got some redundant and inefficient code. Each of the four tests starts by creating a Person object, but they could all share the same Person object. The test_age method needs to create some additional, invalid Person objects to verify the error checking, but there's no reason why it can't share the same "normal" Person object as the other three test methods.

Test::Unit makes it possible to refactor shareable code into a method named setup. If a test class has a setup method, it will be called before any of the assertion methods. Conversely, any clean-up code that is required after each test method runs can be placed in a method named teardown.

Here's a new implementation of PersonTest that uses setup and class constants to remove the duplicate code:

	# person2.rb
	require File.join(File.dirname(__FILE__), '..', 'app', 'person')
	require 'test/unit'

	class PersonTest < Test::Unit::TestCase
	  FIRST_NAME, LAST_NAME, AGE = 'Nathaniel', 'Talbott', 25

	  def setup
	    @person = Person.new(FIRST_NAME, LAST_NAME, AGE)
	  end

	  def test_first_name
	    assert_equal FIRST_NAME, @person.first_name
	  end

	  def test_last_name
	    assert_equal LAST_NAME, @person.last_name
	  end

	  def test_full_name
	    assert_equal FIRST_NAME + ' ' + LAST_NAME, @person.full_name
	  end

	  def test_age
	    assert_equal 25, @person.age
	    assert_raise(ArgumentError) { Person.new(FIRST_NAME, LAST_NAME, -4) }
	    assert_raise(ArgumentError) { Person.new(FIRST_NAME, LAST_NAME, 'four') }
	  end
	end

There are lots of assertion methods besides the assert_equal and assert_raise method used in the test classes above: assert_not_equal, assert_nil, and more exotic methods like assert_respond_to. All the assertion methods are defined in the Test::Unit::Assertions module, which is mixed into the Test::Unit::TestCase class.

The simplest assertion method is just plain assert. It causes the test method to fail unless it's passed a value other than false or nil:

	def test_first_name
	  assert(FIRST_NAME == @person.first_name)
	end

assert is the most basic assertion method. All the other assertion methods can be defined in terms of it:

	def assert_equal(expected, actual)
	  assert(expected == actual)
	end

So, if you can't decide (or remember) which particular assertion method to use, you can always use assert.

See Also



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows