What is BDD and TDD?
When it comes to testing our specs and code, Rails developer have two mainstream choices. We can use Test::Unit, which is supplied by the Ruby Standard Library and is immediately supported by Rails, or we can use the RSpec gems and plugins. Although either one can get the job done, they vary significantly in how they approach testing.
Test::Unit supports a Test Driven Development (TDD) approach, while RSpec implies a Behavior Driven Development (BDD) approach. In TDD, we are usually writing one test per function, leading to many small tests. In TDD, since there is one test per function, we end up writing tests at a core level that end up following the same structure as our code. With a BDD approach, we are using specs as a guide for testing. We are telling a story by describing how a set of functionality should behave, and focus less on the return value of a single method. Think of BDD as a humanized TDD; it is TDD written to use a more English like vocabulary for expressing functionality. BDD makes it easier for other developers to read and understand the code, becoming an additional form of documentation. BDD changes the way we think about testing, since it is more focused on implementing high level specs.
Turning our attention back to Rails, we have access to both Test::Unit and RSpec testing frameworks. Test::Unit is more focused on TDD, while RSpec is more focused on BDD. It is possible to use a BDD approach with Test::Unit, though doing so wouldn't be as clear and concise as using RSpec. Each has their own pros and cons; for example, future updates may break our RSpec tests, causing developers to go back and update their tests into a working state. Test Unit tests won't be affected since it is part of the Ruby Standard Library and built into Rails. Which ever framework you do choose to use will be sufficient for testing, so it's more a question of your preferred testing ideology.
At Killswitch, we have chosen to use the BDD testing paradigm with RSpec. The following is a brief tutorial in getting up and running with RSpec and adopting the BDD approach.
Getting Started:
1. Create a new Rails app:
rails rspec_demo -d mysql
2. Set up your test database and development databases.
3. Install the RSpec Gem:
sudo gem install rspec
4. Install Plugins (git needed):
script/plugin install git://github.com/dchelimsky/rspec.git script/plugin install git://github.com/dchelimsky/rspec-rails.git
5. Generate RSpec directories in root of application:
script/generate rspec
Using RSpec
First, let's create some models. RSpec gives us a new rspec_model command; it will create the model similar to using the Rails model generator, except it will create RSpec files instead of Test::Unit files:
script/generate rspec_model salesman first_name:string last_name:string total_products_sold:integer script/generate rspec_model category title:string script/generate rspec_model product title:string quantity:integer category_id:integer script/generate rspec_model saleman_product saleman_id:integer product_id:integer amount_sold:integer
Before migrating, add 0 as a default value in the migrations for the following columns: total_products_sold in salesman, quantity in product, amount_sold in salesman_products.
Run our migrations with:
rake db:migrate rake db:migrate RAILS_ENV=test
Next, we need to set up relationships and validations in our models:
# app/models/salesman.rb class Salesman < ActiveRecord::Base has_many :salesman_products has_many :products, :through => :salesman_products validates_presence_of :first_name validates_presence_of :last_name end # app/models/product.rb class Product < ActiveRecord::Base has_many :salesman_products has_many :salesmen, :through => :salesman_products belongs_to :category end # app/models/category.rb class Category < ActiveRecord::Base has_many :products validates_presence_of :title end # app/models/salesman_product.rb class SalesmanProduct < ActiveRecord::Base belongs_to :salesman belongs_to :product end
Open up salesman_spec.rb in spec/models. It should contain the following:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe Salesman do before(:each) do @valid_attributes = { :first_name => "value for first_name", :last_name => "value for last_name", :total_products_sold => "1" } end it "should create a new instance given valid attributes" do Salesman.create!(@valid_attributes) end end
The first line includes the spec_helper which is inside of the spec folder. This file will hold helper methods that all specs will use, I will not be going into detail on creating spec helpers, if you would like to learn more about spec helpers check out the official rspec site.
After the spec helper we have a describe block which will describe a given situation. One can put all their tests in one description block, but I usually like to create multiple description blocks that contains logic for a new record, existing records and relationships. Inside the description block there is a before block that sets up each test with needed data.
There is also an it block inside of the describe block, which is an individual test. One describe block can contain many it blocks. Each it takes a string argument that should describe the test logic it contains. With it being Salesman, the example test would read as "Salesman should create a new instance given valid attributes"
Before moving on, lets make it clear the type of tests this describe block will contain. Each describe block can take in a optional string argument. Lets test a new Salesman object, add "that is new," as the second argument in the describe block. Now the test would read as "Salesman that is new, should create a new instance given valid attributes"
describe Salesman, "that is new," do before(:each) do @valid_attributes = { :first_name => "value for first_name", :last_name => "value for last_name", :total_products_sold => "1" } end it "should create a new instance given valid attributes" do Salesman.create!(@valid_attributes) end end
Try running the spec with:
ruby spec/models/user_spec.rb
You should see:
1 examples, 0 failures
RSpec does not print successful tests, only ones that failed. If there was a error, for example a NoMethodError, RSpec would tell us like so:
NoMethodError in 'Salesman that is new, should create a new instance given valid attributes'
Now since we are creating a new salesman, lets remove the total_products_sold set from the hash. Total products sold is not required, so let's write a new test that will check if a new salesman starts with the value of 0 for total_products_sold. While we're at it, lets change the "value for ...." default strings in the before block to have more realistic data. Currently our spec should look similar to the following:
describe Salesman, "that is new" do before(:each) do @valid_attributes = { :first_name => "Tony", :last_name => "Little" } end it "should create a new instance given valid attributes" do Salesman.create!(@valid_attributes) end it "should create a new salesman with zero total products sold" do end end
Usually in Ruby when we want to make sure an attribute equals a certain value we usually use:
salesman.total_products_sold == 0
Using such syntax would make our tests look similar to the actual code. However, since RSpec has its own syntax similar to English, why not use the RSpec equivalent for more readable tests:
salesman.total_products_sold.should eql(0)
Lets test for zero:
it "should create a new salesman with zero total products sold" do salesman = Salesman.create!(@valid_attributes) salesman.total_products_sold.should eql(0) end
Now when we run our specs, both tests should be a success.
So far we have made sure when valid data is used, our tests will pass. What if we wanted to test for errors? RSpec has syntax for testing errors. In our code we would check if an object is valid with the following:
salesman.valid?
The RSpec equivalent:
salesman.should be_valid
It's pretty straight forward, we are stating the salesman should be valid. We can also test to make sure it is not valid with salesman.should_not be_valid. Lets try it out by adding the following test:
it "should not be valid without a first name" do @valid_attributes[:first_name] = nil salesman = Salesman.new(@valid_attributes) salesman.should_not be_valid end
In the above example we first changed the value of first_name to nil, and then we pass those attributes into a new Salesman object. Then when it reaches salesman.should_not be_valid, it will run through the validations, check to see if it was valid, if not, it will return true and our test will pass!
We can be more detailed and thorough in the previous example by checking to make sure the first_name attribute returns a error. To do this, we simply add the following to the end of the test:
salesman.should have(1).errors_on(:first_name)
We did a few tests for a brand new Salesman, so now let's do some model testing with existing data. Before starting, we will need some data that will be used throughout all our tests. We can use fixtures to create test objects which will add data to our test database. The test database will be reset before each test so changes to data from the previous test won't break the next test. Open up salesmen.yml inside of spec/fixtures and enter the following data:
mays: first_name: Billie last_name: Mays total_products_sold: 356 norris: first_name: Chuck last_name: Norris total_products_sold: 155 simmons: first_name: Richard last_name: Simmons total_products_sold: 186
So far we have created a describe block for a new salesman, next we will create another describe block for existing salesmen. I always separate existing from new for not only organizational purposes, but also for speed purposes. If we were to have many tests in a single describe block and load unnecessary fixtures for every test in a spec, it would lead to really slow tests since the database is reset every time with fixture data. So let's add a new describe block that will contain only the tests requiring fixtures. I have added comments in the code:
describe Salesman, "that exists" do # loads spec/fixtures/salemen.yml fixtures :salesmen before(:each) do # Using fixtures, not needed end it "should have a collection of salesmen" do # !Salesman.find(:all).empty? Salesman.find(:all).should_not be_empty end it "should have three records" do # Salesman.count == 3 Salesman.should have(3).records end it "should find an existing salesman" do salesman = Salesman.find(salesmen(:mays).id) salesman.should eql(salesmen(:mays)) end end
For the last test we are using salesmen(:mays), this will grab the attributes with the title 'mays' from the salesmen.yml file. Since mays has an id of 1, it will tell ActiveRecord to grab the salesman with the id of 1. Next we check to make sure that the salesman object has the same attributes found in the .yml file.
Let's quickly add one more describe block that will test relationships between salesmen and products. Once again we will need fixtures, so open up products.yml and saleman_products.yml and add the following:
# products.yml
oxiclean:
id: 1
title: OxiClean
quantity: 35
category_id: 1 # Cleaning
orangeglow:
id: 2
title: OrangeGlow
quantity: 42
category_id: 1
discosweat:
id: 3
title: Disco Sweat
quantity: 22
category_id: 2 # Video
platinumsweat:
id: 4
title: Platinum Sweat
quantity: 34
category_id: 2
totalgym:
id: 5
title: Total Gym Workout Equipment
quantity: 11
category_id: 3 # Exercise Equipment
handyswitch:
id: 6
title: Handy Switch
quantity: 23
category_id: 4 # Home
fixit:
id: 7
title: Fix It
quantity: 14
category_id: 5
liquid_diamond:
id: 8
title: Liquid Diamond
quantity: 7
category_id: 5 # Auto
# saleman_products.yml
one:
saleman_id: 1
product_id: 1
amount_sold: 75
two:
saleman_id: 1
product_id: 2
amount_sold: 89
three:
saleman_id: 2
product_id: 3
amount_sold: 87
four:
saleman_id: 2
product_id: 4
amount_sold: 99
five:
saleman_id: 3
product_id: 5
amount_sold: 45
six:
saleman_id: 1
product_id: 6
amount_sold: 81
seven:
saleman_id: 1
product_id: 7
amount_sold: 76
eight:
saleman_id: 1
product_id: 8
amount_sold: 45
Finally add in a new describe block that will load only fixtures needed for testing salesmen and products relationships.
describe Salesman, "with Products" do # Will load 3 fixtures need for testing Salesman Product relationships fixtures :salesmen, :salesman_products, :products before(:each) do # Using fixtures end it "should have products" do salesman = Salesman.find(salesmen(:mays).id) salesman.products.should_not be_empty end it "should have five products" do salesman = Salesman.find(salesmen(:mays).id) salesman.products.should have(5).records # should include will check if product belongs to salesman salesman.products.should include(products(:oxiclean)) salesman.products.should include(products(:orange_glow)) salesman.products.should include(products(:handy_switch)) salesman.products.should include(products(:fix_it)) salesman.products.should include(products(:liquid_diamond)) end it "should not have products from other salesmen" do salesman = Salesman.find(salesmen(:norris).id) # should_not include is the opposite of should include # we are making sure this salesman, doesn't have products that # other salesmen are selling salesman.products.should_not include(products(:disco_sweat)) salesman.products.should_not include(products(:platinum_sweat)) salesman.products.should_not include(products(:orange_glow)) salesman.products.should_not include(products(:liquid_diamond)) end end
Now with your feet soaked into the world of BDD, it is time for you to start writing tests for your own applications. If you would like to continue building over the example above, more validations can be tested and specs can to be written for the other models as well. Be sure to check out the official RSpec site for more info.

