/ tdd

Why and How to write specs?

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

Prominent Design

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.

Change Fearlessly

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][1]

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][2]

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

rspec spec

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.

require 'spec_helper'
 
describe Magazine do
  let!(:articles){
    [
      Article.new("Ruby Mock Web Server", "Rob Styles", :mocking),
      Article.new("Designing with Web Standards", "Jeffrey Zeldman", :design),
      Article.new("Rails' Insecure Defaults", "Adam Baldwin", :framework),
      Article.new("JavaScript Patterns", "Ethan Stefanov", :development),
      Article.new("Rails Composer", "Stoyan Marcotte", :development)
    ]
  }

  let(:magazine){Magazine.new(articles: articles)}
 
  describe "#new" do
    context "with no parameters" do
      it "has no articles" do
        magazine = Magazine.new
        magazine.should have(0).articles
      end
    end

    context "with an array of articles" do
      it "has five articles" do
        magazine.should have(5).articles
      end
    end
  end
 
  it "returns all the articles in a given category" do
    magazine.get_articles_in_category(:development).length.should == 2
  end
   
  it "accepts new articles" do
    magazine.add_article( Article.new("Wrapping Rack Middleware", "Tim Riley", :server) )
    magazine.get_article("Wrapping Rack Middleware").should be_an_instance_of Article
  end
end

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!][3]
[1]: https://www.relishapp.com/rspec/rspec-core/v/3-0/docs/example-groups/basic-structure-describe-it
[2]: https://www.relishapp.com/rspec/rspec-core/v/3-0/docs/hooks/before-and-after-hooks
[3]: https://www.relishapp.com/rspec/rspec-core/docs/helper-methods/let-and-let