Upload
artem-shoobovych
View
230
Download
0
Embed Size (px)
Citation preview
WHY TESTING?
JUNIOR DEVELOPER'S GUIDE1. write code2. write more code3. write event more code!4. run and check5. if it crashes - debug & goto 36. goto 1
WHY TESTING?
Writing unit tests does not give you profitimmediately.
Instead, it does give you great profit in thefuture.
WHY TESTING?
1. descriptive tests substitude documentation2. finding bugs/errors is much faster3. refactoring is safe4. stubbing units still allows you to write logic for them5. continuous integration
WHY TESTING?
JUNIOR DEVELOPER'S GUIDE(for those who tried testing)
1. write code2. write more code3. cover 100% of code with tests!4. run5. if it crashes - debug6. goto 1
WHY TESTING?
MIDDLE DEVELOPER'S GUIDE1. write code2. run3. if it crashes - debug & fix it4. cover it with tests5. goto 1
A GOOD UNIT TEST IS
CONSISTENTSame test, run with same code multiple times,
should always give same results.
A GOOD UNIT TEST IS
INDEPENDENTTest should not change any other objects,except of those, created in the test itself.
A GOOD UNIT TEST IS
DESCRIPTIVE
generated with --format documentation option
emailValidator isValid for valid e-mail address resolves with valid=true for email address that is not valid resolves with valid=false resolves with correct reason for error result resolves with valid=false resolves with correct reason for service down calls error with error code isUnique for non existing email resolves with false for existing email resolves with true
RSPEC: CODE EXAMPLE
RSpec.describe OrderBuilder, type: :model do let(:user) { create :user } let(:dish) { create :dish } let(:date) { random_day }
subject(:order_builder) { OrderBuilder.new(user, Date.parse(date)) }
describe '#initialize' do context 'with date in the past' do let(:date) { random_day_in_past }
it { expect { subject }.to raise_error(ArgumentError, 'Cannot place order in the past.' end
context 'with date in future' do let(:date) { 'Sunday' }
RSPEC: DIRECTORY STRUCTURE
RSpec tests (called "specs") are usually placedin the spec/ directory with the samestructure, as app/ directory structure.
RSPEC: DIRECTORY STRUCTURE
spec ├── controllers │ ├── orders_controller_spec.rb │ ├── restaurants_controller_spec.rb │ ├── user │ │ └── orders_controller_spec.rb │ └── users_controller_spec.rb ├── factories │ ├── orders.rb │ ├── restaurants.rb │ └── users.rb├── models │ ├── ability_spec.rb │ ├── order_spec.rb │ ├── restaurant_spec.rb │ └── user_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── support ├── controller_helpers.rb ├── database_cleaner.rb ├── factory_girl.rb └── request_helpers.rb
RSPEC: DIRECTORY STRUCTURE
factories and support directories are specific to RSpec
RSPEC: TEST COMPONENTS
Each test should start withRSpec.describe, to allow usage of all
other components.
RSPEC: DESCRIBE
Groups test cases and defines the type ofsubject being tested.
RSpec.describe OrdersController do describe '#index' do # ... end end
RSpec.describe OrderBuilder, type: :model do describe '#initialize' do # ... end end
adding a type for subject may add extra features
RSPEC: SUBJECT
Sets the subject being tested.
subject(:order_builder) { OrderBuilder.new(user, Date.parse(date)) }
# use order_builder variable
subject { -> { order_builder.place_order(order_params) } }
# use subject variable
RSPEC: LET
Defines a variable, whose value will becalculated when being used.
let(:user) { create :user } let(:dish) { create :dish }
# random_day method will be called when using date variable only let(:date) { random_day }
# use user, dish and date variables
RSPEC: LET!
Defines a variable with a value immediately.
# create :user will be called right now let!(:user) { create :user }
# use user variable
RSPEC: CONTEXT
Groups tests by the environment, test subjectis being used in. Used for the same subject, but
with different input/dependant values.
context 'with date in the past' do let(:date) { random_day_in_past }
# here the date will equal to random_day_in_past call result end
context 'with date in future' do let(:date) { 'Sunday' }
# and here date will equal to 'Sunday' end
RSPEC: IT
Specifies test case' body.
it { expect { subject }.to raise_error(ArgumentError, 'Cannot place order in the past.'
# it can also have a description it "does not create order if user has one already" do user.orders << create(:order, order_date: date)
expect(order_builder.order).to eq(user.orders.first) end
RSPEC: EXPECT
Specifies test assertion.
expect { subject }.to raise_error(ArgumentError, 'Cannot place order at the weekend.'
expect { subject.method }.to change(Order.count).by(3)
# when used with subject method call: # subject { order.dishes.count } is_expected.not_to eq(0)
FACTORYGIRL
Allows to create mock objects. But requiresdescribing them in
spec/factories/FACTORY_NAME.rb
FACTORYGIRL: FACTORY
FactoryGirl.define do factory :dish do |f| f.name { Faker::Team.creature } f.price { Random::rand(0 .. 100) } f.description 'tasty dish' f.kind :main_course
association :restaurant end end
BEST PRACTICES
GIVEN-WHEN-THEN STRUCTURE# Given:user.orders << create(:order, order_date: date)
# When: order_builder.place_order!
# Then: expect(Order.today.count).to eq(2)
BEST PRACTICES
GIVEN-WHEN-THEN STRUCTURE# Given:let(:user) { create :user } let(:orders) { [ order1, order2 ] }
# When + Then: expect(user.place(orders)).to change(Order.count).by(2)
BEST PRACTICES
INFORMATIVE MESSAGESdescribe OrderBuilder do describe '#place_order!' do subject { -> { order_builder.place_order! } }
context 'with no orders' do it 'places nothing' do expect { subject }.not_to change(Order.today.count) end end
context 'with one order' do let(:orders) { [ create :order ] }
it 'places one order' do expect { subject }.to change(Order.today.count).by( end end
BEST PRACTICES
NO CONDITIONALSAll the situations must be checked by test
cases.
describe OrderBuilder do describe '#place_order!' do subject { -> { order_builder.place_order! } }
context 'with no orders' do it 'places nothing' do expect { subject }.not_to change(Order.today.count) end end
context 'with one order' do let(:orders) { [ create :order ] }
it 'places one order' do expect { subject }.to change(Order.today.count).by( end end
BEST PRACTICES
NO LOOPSReplace them with (multiple) tests.
it 'sets "delivered" status for all orders' do expect(user.orders.today).to all(eq(Order.DELIVERED)) end
it 'does not set "delivered" status for any order' do expect(user.orders.today).not_to all(eq(Order.DELIVERED)) end
BEST PRACTICES
NO EXCEPTION CATCHINGTest should either expect an exception or no
exception to be thrown.
it 'throws ArgumentException' do expect { order_builder.place_order! }.to raise_exception(ArgumentErrorend
it 'does not throw any exception' do expect { order_builder.place_order! }.not_to raise_exception end
BEST PRACTICES
If you face any of these in your tests:
conditionalsloopsexception handling
that means your tests need refactoring
BEST PRACTICES
SENIOR DEVELOPER'S GUIDE(Test Driven Development)
1. write marvelous tests2. write code3. run tests4. if they fail - fix the code5. goto 1