Why write tests ?
Some answers might be
Because I have been told by my manager
Some could be
yay! let's bolt on writing tests because it's cool
But some meaningful reasons could be
Test-Driven Development is a developer practice that involves writing tests before writing the code being tested. Begin by writing a very small test for code that does not yet exist. Run the test, and, naturally, it fails. Now write just enough code to make that test pass. No more.
It gives a strong base for your project or library or even a framework to build upon flawlessly
Over the time every code base increases in size, eventually most of the time is consumed by the refactoring steThe design is constantly evolving and under constant review, though it is not predetermined. Testing gives a prominent design approach and is one of the most significant by-product of Test-Driven Development.
Having a sufficient test suite will give your the confidence to making a change and ensuring the build is green before deploying your change.
How to write tests ?
The basic idea behind testing(with rspec or any other tool) is, it should be seen as design tool rather than a testing tool. When you write a test case it resembles your design and how the design works. Instead of adding more code, document the next responsibility in the form of the next test. Run it, watch it fail, write just enough code to get it to pass, review the design, and remove duplication. Now add the next test, and repeat the cycle which we refer as red/green/refactor
Lets take an example of Article class
We'll use rspec for testing, setup of which is out of scope for this topic.
require 'spec_helper' describe Article do end
This is how we start with a describe block. Our parameter to describe explains what we're testing: this could be a string, but in our case we're using the class name. [Read More on describe]
before :each do @article = Article.new "Title", "Author", :category end
We'll begin by making a call to “before" we pass the symbol :each to specify that we want this code run before each test (we could also do :all to run it once before all tests). Use before and after hooks to
execute arbitrary code, before and/or after the body of an example is run. [Read More]
describe "#new" do it "takes three parameters and returns an Article object" do @article.should be_an_instance_of Article end end
We have our first test ready. Our test simply confirms that we indeed made an Article object. We're using a nested describe block here to say we're describing the actions of a specific method. You might have noticed the string "#new"; it's a convention in Ruby to refer instance methods like this: ClassName#methodName. Since we have the class name in our top-level describe, we're just putting the method name here.
Now fire up a terminal, cd to the project directory and run
You should see output saying something about "uninitialized constant Object::Article"; this just means there's no Article class. Let's fix that.
As per TDD, we only want to write enough code to fix this problem. In the article.rb file, that would be this:
class Article end
Re-run the test (rspec spec), and you’ll find it's passing fine. Green
Some more tests for Article
describe "#title" do it "returns the correct title" do @article.title.should eql "Title" end end describe "#author" do it "returns the correct author" do @article.author.should eql "Author" end end describe "#category" do it "returns the correct category" do @article.category.should eql :category end end
These will fail, as the Article object doesn't respond to title, author, category. So here’s the code for Article to make them pass:
class Article attr_accessor :title, :author, :category def initialize(title, author, category) @title = title @author = author @category = category end end
We can take this further to create a Magazine of Articles. Where a magazine would comprise of many articles. And we could write up more tests and add a lot of other functionality. Following are some examples specs that we can write for Magzine.
You can take this as an exercise to make this test cases pass.
In the above example we have used let and let!.
What is let ?
Code within a let block is only executed when referenced. This may resemble lazy loading, means that ordering of these blocks is irrelevant but as invoked only when referenced in code. Notable aspect about let block is that the last let block defined in the current context will be used. This is good for setting a default to be used for the majority of specs, which can be overwritten if needed.
What is let! ?
Execution of let! blocks is in order of its declaration (much like a before block). The one core difference is that you get an explicit reference to this variable, rather than needing to fall back to instance variables.
Read More about [let and let!]