marp |
---|
true |
День 2
- Почему нужны автотесты
- Почему разработчики избегают тесты
- Основы RSpec
- Тестирование Model
- Factory Bot
- TDD
- BDD
- Синтаксис RSpec
- Тестирование Requests
- Приниципы хорошего тесты
- Рефакторинг тестов
- Красный: написать падающий тест
- Зеленый: написать минимальный код для прохождения теста
- Рефакторинг: улучшить код, сохраняя зеленый
RSpec.describe Episode do
subject(:episode) { described_class.new(duration:) }
describe "#duration_in_minutes" do
subject { episode.duration_in_minutes }
context do
let(:duration) { 60 }
it { is_expected.to eq 1 }
end
end
end
class Episode
def inialise(duration:)
@duration = duration
end
def duration_in_minutes = 1
end
class Episode
def inialise(duration:)
@duration = duration
end
def duration_in_minutes = @duration / 60
end
- маленькие шаги
- постоянный рефакторинг
- эволюционный дизайн
- исполняемая документация
- минимальность кода
- написание теста в конце
- выбор большого шага
- Тесты должны объяснять, что делает система, а не как она это делает
- Тесты читаются как сценарии из реального мира
- Разработчики, тестировщики и бизнес говорят на одном языке
Feature: Episode listening
As listener
I want to pause episode
To continue later
Scenario: Pause in a middle of episode
Given I have episode palying
When I pause episode at 20 mitute
Then episode saves current position
describe "Episode listening" do
let(:listener) { Listener.new }
let(:episode) { Episode.new(duration: 1.hour) }
it "pauses in a middle of episode" do
# Given
episode.start_playback(listener)
# When
episode.pause_playback(listener, at: 20.minute)
# Then
expect(episode.last_pause_position(listener)).to eq(20.minute)
end
end
RSpec.describe Episode do
# Bad
it "episode pause in a middle"
# Good
it "pauses in a middle of episode" do
end
it "pauses in a middle of episode" do
# Bad
expect(episode).to receive(:cache_playback).with(20.minutes)
episode.start_playback(listener)
episode.pause_playback(listener, at: 20.minutes)
expect(episode.last_pause_position(listener)).to eq(20.minutes)
end
describe "Episode duration" do
subject { episode.duration }
end
describe "Episode duration" do
subject { episode.duration }
it { expect(subject).to eq 10 }
it { is_expected.to eq 10 }
end
describe Episode do
subject(:episode) { descibed_class.new }
it { is_expected.to be_new }
describe "#duration" do
subject { episode.duration }
it { expect(subject).to eq 10 }
it { is_expected.to eq 10 }
end
describe "Episode duration" do
subject { episode.duration }
context "when episode has no content" do
before { episode.content = nil }
it { is_expected.to eq 0 }
end
context "when validation errors" do
it "is not valid without title"
it "is not valid without author"
end
end
let(:episode) { create(:episode) }
let(:user) { create(:user) } # not created
it { expect(episode).to be_an(Episode) }
describe "found items" do
subject { Episode.recent }
context do
let(:episode) { create(:episode) }
before { episode } # create episode
it { expect(subject.size).to eq 1 }
end
context do
let!(:episode) { create(:episode) }
it { expect(subject.size).to eq 1 }
end
end
expect(1).to eq(1)
expect(1).to eql(1)
expect(1).to equal(1)
expect(1).to be_a(Number)
expect(1).to be_an(Integer)
expect(1).to be_a_kind_of(Number)
expect(found_item).to be_nil # found_item.nil?
expect(episode).to be_published # episode.published?
expect("Long Title).to match(/Title.+/)
expect("Long Title").to match("Long")
expect("Title with dot.").to end_with(".")
expect("Title Here").to start_with("Title")
expect { do_something_risky }.to raise_error
expect { do_something_risky }.to raise_error(PoorRiskDecisionError)
expect { podcast.add_episode(episode) }.to change(podcast, :size).from(0).to(1)
expect { podcast.add_episode(episode) }.to change(podcast, :size).to(1)
expect { podcast.remove_episode(episode) }.to change(podcast, :size).by(-1)
expect { podcast.add_episode(episode) }.to change{ podcast.reload.size }.by(1)
RSpec::Matchers.define :be_published do
match do |episode|
episode.published? && episode.publication_date <= Time.current
end
end
it "publishes episode" do
episode.publish!
expect(episode).to be_published
end
RSpec::Matchers.alias_matcher :be_empty, :be_nil
expect(search_results).to be_empty
hash = {
a: {
b: ["foo", 5],
c: { d: 2.05 }
}
}
expect(hash).to match(
a: {
b: a_collection_containing_exactly(
a_string_starting_with("f"),
an_instance_of(Fixnum)
),
c: { d: (a_value < 3) }
}
)
describe "GET /index" do
it "works" do
get podcasts_url
end
end
describe "GET /index" do
it "renders a successful response" do
get podcasts_url
expect(response).to be_successful
end
end
- request
- response
- header
- body
- status
- side effect
- авторизация
- ожидание только важного
- тестирование валидации
- тестирование i18n
- тестирование Model
- чистый код
- тест как история
- Arrange Act Assert
- быстрый
- независимый
- повторяемый
- скрытые нерелевантные детали
- одна проверка
RSpec.shared_examples 'engine startable' do
it 'starts engine' do
expect(subject.start_engine).to eq(true)
end
end
it_behaves_like 'engine startable'
include_examples 'engine startable'
RSpec.shared_context 'order setup' do
let(:item1) { double('Item', price: 10) }
let(:item2) { double('Item', price: 20) }
let(:order) { Order.new([item1, item2]) }
end
include_context 'order setup'
RSpec::Matchers.define :include_item do |id|
match do |subject|
subject.map(&:id).to include(id)
end
end
it { is_expected.to inlucde_item(podcast.id) }