[TOC]
Three layers of tests:
We should have more unit tests, then integration tests, and then end-to-end tests.
I also like what Kent Dods says in https://testingjavascript.com:
Install globally:
gem install rspec
Start a new project
mkdir rspec-course
cd rspec-course
rspec --init
The "init" will create .rspec and spec/spec_helper.rb
It's a top-down approach, where you first think about how you wanna use a piece of software, and then write the software.
Red -> Green -> Refactor
graph LR Red --> Green --> Refactor --> Red
What are the benefits of TDD?
It forces you to become a better developer. Simply practicing this thing is one of the best ways that I have become a better developer and matured as a programmer, especially in my object oriented thinking.
You don't have to read additional blog posts. This is something that you can do every day. Whenever you write code, just write your tests first.
describe method - example groupThe describe method creates an example group.
RSpec.describe 'Card' do
end
describe methoddescribe method:
'Card' stringdo-end blockdo-end block is where we will write all of our tests for the Card.A test is also known as an "example". And "example group" is a set of related tests.
it method - a single exampleThe describe creates an example group, the it method creates a single example.
RSpec.describe 'Card' do
it 'has a type' do
end
end
The idea is to describe how the software should behave, instead of saying how it should be implemented.
Note: the specify method has the exact same meaning as it.
expect methodDoing assertions with expect.
RSpec.describe 'Card' do
it 'has a type' do
card = Card.new('Ace of Spades')
expect(card.type).to eq('Ace of Spades')
end
end
Create an example group with a string argument of "math calculations".
Inside the group, create an example with a string argument of "does basic math".
Inside the example, write 4 mathematical assertions of your choice.
The expect method should receive a valid mathematical expression (for example, 3 + 4 or 5 * 3).
The eq method should compare the result fo the evaluation with the right answer.
RSpec.describe 'math calculations' do
it 'does basic math' do
expect(MyMath.plus(3, 4)).to eq(7)
expect(MyMath.minus(3, 4)).to eq(-1)
expect(MyMath.multiply(3, 4)).to eq(12)
expect(MyMath.divide(8, 4)).to eq(2)
end
end
# run all tests in the spec/
rspec
# run a specific test file (aka example group)
rspec spec/card_spec.rb
# run a specific test (aka example)
rspec spec/card_spec.rb:2
Create a class based on this test suite (example group):
RSpec.describe School do
it 'has a name' do
school = School.new('Beverly Hills High School')
expect(school.name).to eq('Beverly Hills High School')
end
it 'should start off with no students' do
school = School.new('Notre Dame High School')
expect(school.students).to eq([])
end
end
Consider this example group, with two tests, both of them instantiating a Card:
RSpec.describe Card do
it 'has a rank' do
card = Card.new('Ace', 'Spades')
expect(card.rank).to eq('Ace')
end
it 'has a suit' do
card = Card.new('Ace', 'Spades')
expect(card.suit).to eq('Spades')
end
end
In order to prevent duplication we're going to use an instance variable and assign a value to it in a before method:
RSpec.describe Card do
before do
@card = Card.new('Ace', 'Spades')
end
it 'has a rank' do
expect(@card.rank).to eq('Ace')
end
it 'has a suit' do
expect(@card.suit).to eq('Spades')
end
end
Another way to reduce duplication is to use a helper method. In this example, such method is called card:
RSpec.describe Card do
def card
Card.new('Ace', 'Spades')
end
it 'has a rank' do
expect(card.rank).to eq('Ace')
end
it 'has a suit' do
expect(card.suit).to eq('Spades')
end
end
Although it seems interesting, it can bring problems. Like the one explained below:
class Card
# rank can be changed
attr_accessor :rank, :suit
def initialize(rank, suit)
@rank = rank
@suit = suit
end
end
RSpec.describe Card do
def card
Card.new('Ace', 'Spades')
end
it 'has a rank and that rank can change' do
expect(card.rank).to eq('Ace')
card.rank = 'Queen' # this is actually create a new Card
expect(card.rank).to eq('Queen') # again, creating a new Card
end
end
Every time a card is used, it gives an impression that it's an object but it's actually a method, creating new Card objects everytime it's called.
let methodUses memoization to create an object.
let(:card) { Card.new('Ace', 'Spades') }
It uses lazy loading, therefore better than using before. Why? Because before runs before every single test, while using let makes that block to run only when the symbol passed to let is called.
context method and nested describesdescribe can be nestedcontext is a synonym for describeExample:
RSpec.describe '#even? method' do
context 'with even number' do
it 'should return true' do
expect(4.even?).to eq(true)
end
end
context 'with odd number' do
it 'should return false' do
expect(5.even?).to eq(false)
end
end
end
before and after hooksThe code below is pretty descriptive. Just keep in mind that "context" is a synonym to "describe" and that "example" refers to each "it" block.
RSpec.describe 'before and after hooks' do
before(:context) do
puts 'Before context'
end
after(:context) do
puts 'After context'
end
before(:example) do
puts 'Before example'
end
after(:example) do
puts 'After example'
end
it 'is just a random example' do
expect(4 * 5).to eq(20)
end
it 'is just another random example' do
expect(3 - 2).to eq(1)
end
end