ruby learning tdd testing

Getting Started: No Background Knowledge Required1

Learning Ruby With Tests

  • : motivation and overview
  • Part 1: class, method, if … else and a test system
  • : predicates, variable scopes, initialize

My (yes, that's a pun) has met the real world with quite satisfying results today. The candidate was familiar with the basic outline of a Ruby class, how a method looks like, how to do an if…else statement and what an instance variable is. That's it. However, dear learner, the point is that you'll learn all of this through tests, so let's get started.

1 I do, however, assume that you know how to open a terminal window and that you have Ruby installed (this Howto doesn't care about the Ruby version). You should also have a simple texteditor installed, preferably one with syntax highlighting for the Ruby language.

A Simple Rubyfile

Ruby doesn't really care about the folder- and file-structure of your code. We'll just create a new folder in your home directory and create a single file. Run the following commands in you terminal:

cd ~                  # Moves into your current user's home dir
mkdir crashtest       # Creates a directory named crashtest
cd crashtest          # Change into the new directory
touch crash_test.rb   # Create an empty file called crash_test.rb

Now, open crash_test.rb in your editor of choice.

Travelling First Class2

Ruby is an object-oriented programming language. It means that it tries to model logic as general concepts, templates if you will. You can create so called instances from these templates, representing an actual object of that class. In Ruby, everything is an object. Don't worry too much about the difference of classes and instances. At this point it's sufficient to accept that the class is the template and that you can create … things from such a template.

Creating a class is simple. Let's start with a simple one where we aim to model a Ruby representation of a vehicle. A class keyword, followed by the CamelCased name of the class and a closing end is all we need:

# File: crash_test.rb
class Vehicle
end

2 Whoa, now it becomes evident that the headline was indeed pun #2

The Vehicle class defines a generic template for vehicles. Actually, it doesn't define much at all since it's still empty, but we can already create instances from our class:

# File: crash_test.rb
class Vehicle
end

# Create a new instance
vehicle = Vehicle.new

What happens in the last line? vehicle is a variable and we assign it a value. Ruby is pretty laid back when it comes to variable names but for simplicity's sake, let's stick with a rule that all variables get a snake_cased name. The new method is a special »class method« called on our Vehicle class. It returns an instance of that class and this is what we assign to the variable name vehicle3

3 We could have named the variable anything really, but it's good practice to give a variable a descriptive name – and what could be more descriptive for an instance of the Vehicle class than the lower cased class name?

Note that we didn't explicitly define the new method (ie. our Vehicle class looks quite empty). In object oriented programming, there is a thing called inheritance where a class can inherit behaviour from related objects further up the object hierarchy. It's invisible for our Vehicle class but we'll get there soon enough.

Interlude: About Softwaretests

The idea about automated software tests is to write program code that checks whether or not a certain class or one of their methods does what it should. Oftentimes a single method behaves differently depending on its input and the aim of writing tests for that component is to cover all edge cases. Also, you can re-run the test at your leisure which becomes really important when you tamper with your program code later on, for instance to add or change functionality.

For example, a program is to calculate the square root for it's input value. A test can make sure that it does indeed calculate the square root for a fixed number, but also makes sure that the program catches illegal input such as negative values (we're being rational here). It does that by calling the program with the potentially offending input and comparing an expected result with the program's actual result.

Test Driven Development (or TDD for short) takes this concept a step further. By writing test cases before the actual program is implemented, it guides the developer in building his or her software in small understandable pieces. It also makes sure that the tests do indeed test what you're plannung to implement: you need to see them fail first, then implement the desired behaviour, then have your tests pass (also known as Red/Green/Refactor).

A Simple Test

So, aiming to become good TDD-citizens, we need to write a test before we add functionality to our Vehicle class. How do we do that? Ruby ships with a test framework called Test::Unit and there are numerous others around, with RSpec being a very popular one. Following my initial motivation and my assumption that using one of these only adds more vocabulary, we'll use neither. Instead, we'll write a simple testing method on our own.

def assert expression
  # Method body, implementation goes here
end

The code snippet above uses the def keyword to define a method, followed by the desired method name. We name it »assert«. Our method is defined as expecting a single argument: expression. Whatever is passed as an argument to our assert method will be available as a variable with the same name in the method block. Right now, however, assert doesn't do anything so let's fix that.

def assert expression
  if expression
    puts '.'  # Only executed when `expression´ evaluates to true
  else
    puts 'F'
  end
end

We introduced an if … else statement. It outputs a single dot in your terminal when expression (the parameter passed to assert from whatever source) evaluates to true and a single 'F' otherwise. true and it's counterpart false represent basic logic elements you may have encountered in maths or philosophy before. When we see only dots, we're in the green (we can add fancy coloured output later).

Vehicle Gets Its First Method

First, let's have a look at our crash_test.rb file so far:

# File: crash_test.rb
class Vehicle
end

def assert expression
  if expression
    puts '.'  # Only executed when `expression´ evaluates to true
  else
    puts 'F'
  end
end

Now that we have a means to test, we can get to work. Let's say we want every instance of vehicle to have a method called velocity, returning the current … well … velocity of the vehicle. Let's also define that every newly created vehicle starts with a velocity of 0 (ie. it stands still). A test case using our very own assert could look like this:

# Code to define Vehicle and `assert´ omitted

# Expects a fresh Vehicle instance to have a velocity of 0
vehicle = Vehicle.new
assert vehicle.velocity == 0 

The == checks for equality. Don't mix it up with the single equal sign which assigns a value. The returned value of the == operator is either true or false (two values are either equal or they aren't), just what the doctor ordered and - coincidentally - what we expect as an input parameter for our assert method.

Now that we have our first test, let's run it. Head back to your terminal window and issue the following command:

ruby crash_test.rb

The command will output an error:

crash_test.rb:14:in `<main>': undefined method `velocity' for #<Vehicle:0x007fda7b13a238> (NoMethodError)

The Ruby interpreter complains about the absence of a method named velocity. Fair enough, we haven't created it yet. Let's remedy that:

# File: crash_test.rb
class Vehicle
  def velocity
  end
end

# Rest of the file omitted

Re-run the file with the Ruby interpreter:

ruby crash_test.rb

It outputs a single »F« on the screen, showing us that our assertion of vehicle.velocity being equal to 0 fails. But instead of receiving an error from the Ruby interpreter, we now have a feedback from our test system and that is an improvement.

The velocity method in our Vehicle class has an empty method body. Every Ruby call returns something (implicitly the value of its last line). Even when it doesn't return something, it will return nil, a fellow you'll see often. So our velocity method returns nil when we expect it to return 0. That's easy to fix, let's just return the 0 explicitly then:

# File: crash_test.rb
class Vehicle
  def velocity
    0 # A method returns its last statement
  end
end

def assert expression
  if expression
    puts '.'  # Only executed when `expression´ evaluates to true
  else
    puts 'F'
  end
end

# Expects a fresh Vehicle instance to have a velocity of 0
vehicle = Vehicle.new
assert vehicle.velocity == 0 

Re-run the testfile again and you'll see a single dot which means we're passing! Congratulations, you've just done your first TDD.

Summary And Outlook

The first part covered the basic layout of a class, a method within that class and an instance of the class on which that method got called. Last but not least we created a very basic tool to test the functionality of our software, out assert method.

Stay tuned for the where we will extend out test tools, learn about instance variables and add further functionality to our Vehicle class.

comments powered by Disqus