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 persons 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, lets 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 	est/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, lets 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 its 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 theres no reason why it can 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.

Heres 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 	est/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 its 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 decide (or remember) which particular assertion method to use, you can always use assert.

See Also

  • ri Test::Unit
  • The documentation for the Test::Unit library is also online at http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/index.html
  • Recipe 15.22, "Unit Testing Your Web Site"
  • Recipe 17.8, " Running Unit Tests"
  • Recipe 19.1, "Automatically Running Unit Tests"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net