TDD and UnitTests in ruby

I’m relatively new to ruby, but one thing I noticed pretty much from the start is that the community surrounding this language are incredibly passionate – not only about the language itself but about coding practices and development techniques. One of the things they are particularly vocal about is Test Driven Development (TDD). I personally have never used TDD in any of the projects I’ve been involved with, but the philosophy absolutely fascinates me so I will definitely be checking it out on whatever my next venture happens to be.

For anyone who needs a quick primer: TDD basically centres around the belief that if we code up a bunch of tests stating how we expect our code to behave then all we need to do is to write our code such that those tests pass, making it easier to see when we’re done and providing a constant test-bed for the future. Two immediate practical uses of this immediately spring to mind;

  • It can act as a sort of class blueprint which you work towards.
  • Keeps a constant check that you haven’t mucked anything up!

Unit testing

There are many types of test (the ruby on rails framework, for example, even lets you test your views!) but here I’m going to focus on unit testing – the act of testing units of source code to make sure they do what they’re meant to.

Let’s do the classic example. We’re coding up a “person” class. We start by creating a class stub like so…

class Person
end

Then in a separate file, create the unit test.

require "test/unit"
require "person"

class TestPerson < Test::Unit::TestCase

  def test_person_methods
    #assertions go here
  end

  def test_person_functionality
    #assertions go here
  end

  #more tests...
end

As with most things in ruby (and helped greatly by its reflective nature) they’ve made unit testing incredibly easy. After requiring the “test/unit” library and inheriting from the Test::Unit::TestCase class, you simply make a bunch of methods prefixed with;

test_

Each of these will be treated as a seperate test. As with most Unit Testing frameworks, any assertions which fail within a test will result in that test failing. After you’ve set up all of this, you just run the file with the tests in to get the results of the test!

An Example

Here’s an example of a test that would be able to see if methods existed within a class. You could use this as a test to work towards as you first create a class, but also if you ever want to change a method name or add a new method later. Imagine you decided that the Person class needed a name, age, firstname and lastname;

  def test_methods_exist
    #create a new person
    person = Person.new

    #create a literal array of the methods we expect to see
    expected_methods = %w{ age name firstname lastname }

    #run an assertion on each method stating that the method exists
    expected_methods.each do
      |method|
      assert(person.respond_to?(method), "#{method} does not exist")
    end
  end

I call the “assert” method with two arguments: the first must evaluate to a boolean value – if this returns false then the second optional parameter (which all assertions can have, by the way) specifies a custom message to display should the test fail. Here I’ve used interpolation to display a meaningful message about the method not existing (rather than just the “computer says no” response you’d get otherwise). Other, more specific assertions come with pretty good messages anyway, but you’ll always want to specify a message when using a vanilla assertion.

When you run the code you get output like this;

Loaded suite person-tests
Started
F
Finished in 0.021184 seconds.

  1) Failure:
test_methods_exist(TestPerson)
    [person-tests.rb:16:in `test_methods_exist'
     person-tests.rb:14:in `each'
     person-tests.rb:14:in `test_methods_exist']:
age does not exist.
 is not true.

1 tests, 1 assertions, 1 failures, 0 errors

It’s failed because the age method does not exist, as can be clearly seen from the output. Once we go back and add in those methods, the test passes;

Loaded suite person-tests
Started
.
Finished in 0.001792 seconds.

1 tests, 4 assertions, 0 failures, 0 errors

Another Example

You could use the same technique but apply it to the functionality of the Person class. Hopefully it’ll be self explanatory, but i should point out that the following example does not follow best practices! TDD best practices is a whole other can of worms, all I’m trying to do here is demonstrate the core assertions that are possible. If you were to run the below without having implemented the various methods, for example, your test class itself would raise an exception. Anyway, here goes;

  def test_person_functionality
    #set up a person
    person = Person.new
    person.firstname = "Mikey"
    person.lastname = "Hogarth"
    person.age = 30

    #check that the correct class was created
    assert_instance_of(Person, person)

    #check that "name" will return the first and last names appended
    assert_equal("Mikey Hogarth", person.name)

    #check that the age range is limited 0...200
    assert_raises(RangeError) { person.age = -1 }
    assert_raises(RangeError) { person.age = 201 } 

  end

Pretty noddy example and probably driving TDD advocates potty with agitation, but hopefully it demonstrates the functionality.

That pretty much wraps it up – anyone interested in what the person class might have looked like in order to pass the tests, here it is;

class Person

  attr_accessor :firstname, :lastname
  attr_reader :age

  def name
    @firstname + " " + @lastname
  end

  def age=(val)
    raise RangeError if(val > 200)
    raise RangeError if(val < 0)
    @age = val
  end

end

Here’s something awesome to close with. I had previously mentioned that the ruby community is very passionate – it should also be pointed out that they are completely nuts, as the following demonstrates!

If you wanted to create a test stub that you were going to come and finish up later, you are encouraged to do so using the following method;

  def test_person_is_valid
    flunk
  end

This is, 100% genuine, part of the standard ruby library. That method causes you to flunk the test. I’ll close with the output 🙂

Started
..F
Finished in 0.004339 seconds.

  1) Failure:
test_person_is_valid(TestPerson) [person-tests.rb:40]:
Flunked.

3 tests, 9 assertions, 1 failures, 0 errors
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s