Getting Started: No Background Knowledge Required1
Learning Ruby With Tests
My approach of using only minimal Ruby knowledge and putting it to a test (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
end2 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.rbThe 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 omittedRe-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 next part where we will extend out test tools, learn about instance variables and add further functionality to our Vehicle class.
comments powered by Disqus