Skip to content

Instantly share code, notes, and snippets.

@guih
Created March 15, 2017 13:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guih/e113c62d7ce2968a07d23a4790ed47ab to your computer and use it in GitHub Desktop.
Save guih/e113c62d7ce2968a07d23a4790ed47ab to your computer and use it in GitHub Desktop.
FloripaOnRails Meetup - TDD
# =============
# What is TDD ?
# =============
# Writing test first is not TDD, you should let your test guide your code
# so the test bellow should have the simplest implementation as possible
it 'consumes fuel' do
expect { drive(80) }.to change(car, :fuel).from(10).to(2)
end
# not TDD implementation
def drive(distance)
fuel_to_consume = distance / @car_effiency
fail "Not enough fuel" if fuel < fuel_to_consume
@fuel -= fuel_to_consume
end
# TDD implementation
def drive(distance)
@fuel -= 8
end
# Then, you should write another spec that makes you change this implementation.
# Of couse this is the ludic version of TDD, when you got some practice, you can
# skip these very basic steps, but you should defenitely try the ludic one as an
# exercise. I can assure you that you'll be surprized how the implementation
# is different from the implementation without TDD.
# =============================
# TDD tips and tricks (example)
# =============================
RSpec.describe Car do
# Start declaring the subject after describe
# Use described_class variable
subject(:car) { described_class.new(parameters) }
let(:parameters) { {} }
# driver is not used in this example, I just wanted to show a FactoryGirls usage.
let(:driver) { FactoryGirl.build_stubbed(:driver) }
# Organize validations in a separated describe group
describe 'validations' do
it { is_expected.to validate_presence_of(:efficiency) }
it { is_expected.not_to allow_value(-1).for(:efficiency) }
it { is_expected.to allow_value(12).for(:efficiency) }
end
describe '#drive' do
# Again, start with subject after a describe block
subject(:drive) { car.drive(distance) }
let(:distance) { nil }
# Start with exceptions and unhappy paths
context 'when the car is not valid' do
it { is_expected.to raise_error 'Invalid car. Please check car.errors for more information' }
end
# Almost every context should have an opposite context
context 'when the car is valid' do
let(:parameters) { {efficiency: 10, fuel: 10} }
# Write a specification for what you're testing.
# For example, if you read the context and the expectations for this context,
# you will read: "When Car#drive when the car is valid, when the distance is
# above the car's current autonomy, then it consumes all the fuel and it returns
# the walked distance".
# That is totally human readable.
context "when the distance is above the car's current autonomy"
let(:distance) { 1000 }
it 'consumes all the fuel' do
is_expected.to change(car, :fuel).from(10).to(0)
end
it 'returns the walked distance' do
expect(drive).to eq ( 100 )
end
end
context "when the distance is below the car's current autonomy" do
it { is_expected.to change(car, :fuel).from(10).to(2) }
end
end
end
end
# If your project is already a monster and you're afraid to start using TDD
# consider starting it with the Bugs. Whenever you're going to fix a Bug, try
# to write a spec for the broken scenario first, then run the spec, see it failing
# and only then, you should fix it and see it the spec passing.
# Also, you should try to start creating factories to reduce the time to setup your specs.
# Last but not least, "Never trust a spec you didn't see fail", it's really common to write
# a spec that just doesn't test anything.
# There's more at https://speakerdeck.com/guih
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment