Too Cool for Internet Explorer

Sunday, February 10, 2008

Ruby from Test::Unit to RSpec part 1 of 3

What is this all about ? Coding tests for applications ? Why ?

The technical view:

  • Avoid creating a bug when fixing another one.
  • Don't use to play with the program to find errors (it is slow).
  • Unit tests are the first step on TDD (test driven development).

The programmer view:
  • I will not write tests, that is what testers do.
  • Writing tests is a waste of time, it slows down my productivity.
  • My code works, I'm a good programmer, and I don't need to write tests to prove it.

The tester view:
  • Programmers couldn't write tests, that is what testers do.
  • If programmers write tests where our jobs will go ?
  • If you ask a programmer to write tests you know nothing about tests.
Unit tests before code, advantages:

Creating unit tests before the program solution, developers will consider what need to be done. Requirements become stronger when supported by tests. Couldn't be misunderstanding on a specification written in the form of executable code.

The feedback is immediate while the work goes on. Creating unit tests first, developers can feel when all necessary conditions and functionality have being considered and implemented.

There is also a benefit to system design. Creating tests first your software design will be influenced by a desire to test everything valuable to the customer, reflecting on an easier to test design.

How to create a Unit test ?

  • Require 'test/unit' in your test script.
  • Create a class that subclasses Test::Unit::TestCase.
  • Add a method that begins with "test" to your class.
  • Make assertions in your test method.
  • Optionally define #setup and/or #teardown to set up and/or tear down your common test fixture.
Comes from Google an example from the Ruby/Password library's unit tests.

require 'test/unit'
require 'password'

class TC_PasswordTest < Test::Unit::TestCase

def test_check
# Check for a weak password.
pw = 'foo' )
assert_raises( Password::WeakPassword ) { pw.check }

# Check for a good password.
pw = 'G@7flAxg' )
assert_nothing_raised { pw.check }

# Check for an exception on bad dictionary path.
assert_raises( Password::DictionaryError ) { pw.check( '/tmp/nodict' ) }


This is just a basic sample, but that is the idea for now.

You can search for available Ruby assertions on Ruby-Doc.

Test Case is a class that inherits from Test::Unit::TestCase, and contains a "testing strategy", comprised of contextually related tests, and each test is a method named test_something.

Test Suite is a collection of test cases. When a test suite is executed, each test belonging to it is executed. A suite is excellent for continuous-build integration for example. Tests could be aggregated on a suite when needed.

Test Fixture is a way to group "setup" and "teardown" methods, to prepare your test unit framework to prepare the environment to your tests.

Test Harness is a collection of software and test data configured to run unit tests under varying conditions monitoring its bahavior and outputs.

Assertions are the heart of unit testing, providing a way to define expected results for a test. The goal is to test the behavior, by setting the test to pass or fail according to an expected type and value.

Mock Objects mimics behavior of real objects on controlled environments, programmers can use these objects to meet the interface requirements on complex real functionalities, without calling complex underlying or collaborating classes.

Test Unit and command line options:

To see all options just call a test with the -h option.

ruby my_test.rb -h

The best options to try are:

-r which brings up a GUI interface.
-n and -t which specifies specific methods and classes to be tested

More on command line options in this Scott Patten's article.

No comments: