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:
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:
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:
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 vehicle
3
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.
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.
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:
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:
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:
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:
Re-run the file with the Ruby interpreter:
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:
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