расскажу про trait
первые указывают сколько раз будет вызван метод : once, exactly, at_least, at_most, и any_number_of_times и это только для моков!!!
вторые что передаем и что ожидаем with, and_return
плохой пример так как методы калькулятора нет смысла макать, и проверка вызова метода раз или 2 показана плоха:(
require 'rspec'
require_relative '../lib/calculator'
RSpec.describe Calculator do
let(:calculator) { Calculator.new }
describe '#add' do
it 'adds two numbers' do
expect(calculator.add(2, 3)).to eq(5)
end
it 'add runs once' do
allow(calculator).to receive(:add).with(2, 3).and_return(5)
# настраивает мок-объект calculator так, чтобы метод add возвращал значение 5 при вызове с аргументами 2 и 3
expect(calculator).to receive(:add).once
# настраивает ожидание, что метод add будет вызван ровно один раз.
calculator.add(2, 3)
# вызываем метод add
end
it 'add runs two' do
allow(calculator).to receive(:add).with(2, 3).and_return(5)
# настраивает мок-объект calculator так, чтобы метод add возвращал значение 5 при вызове с аргументами 2 и 3
expect(calculator).to receive(:add).exactly(2).times
# настраивает ожидание, что метод add будет вызван 2 раза
calculator.add(2, 3)
calculator.add(2, 3)
# вызываем метод add 2 раза
end
end
end
остальные примеры сделаем на анализе ключевой ставки https://www.cbr.ru/hd_base/KeyRate/
На примере нашего любимого калькулятора
class Calculator
def add(a, b)
a + b
end
def subtract(a, b)
a - b
end
def multiply(a, b)
a * b
end
def divide(a, b)
raise ZeroDivisionError if b == 0
a.to_f / b
end
end
require 'rspec'
require_relative 'calculator'
RSpec.describe Calculator do
let(:calculator) { Calculator.new }
describe '#add' do
it 'adds two numbers' do
expect(calculator.add(2, 3)).to eq(5)
end
end
describe 'mocking with receive' do
it 'mocks the add method with allow' do
allow(calculator).to receive(:add).with(2, 3).and_return(5)
# Создает заглушку для метода add который для аргементов 2,3 вернет 5
expect(calculator.add(2, 3)).to eq(5)
# Ожидает что calculator.add(2, 3) вернет 5
end
it 'expects the add method to be called with expect' do
expect(calculator).to receive(:add).with(2, 3).and_return(5)
# Ожидаем что на объекте калькулятор будет вызван метод add с аргументами 2,3 и он вернет 5
calculator.add(2, 3)
end
end
end
class Calculator
def initialize(initial_value = 0)
@result = initial_value
end
def add(a, b)
@result = a + b
end
end
RSpec.describe Calculator do
subject { Calculator.new(10) }
describe "#add" do
it "returns the sum of two numbers" do
subject.add(3, 4)
# eq - проверяет на равенство
expect(subject.result).to eq(17)
# be - проверяет на идентичность (обычно используется для чисел и символов)
expect(subject.result).to be == 17
# eql - проверяет на равенство значений и типов
expect(subject.result).to eql(17)
# equal - проверяет на идентичность объектов
expect(subject.result).to equal(subject.result)
# be_truthy - проверяет, что значение истинно (не nil и не false)
expect(subject.result).to be_truthy
# be_falsey - проверяет, что значение ложно (nil или false)
expect(subject.result).not_to be_falsey
# be_nil - проверяет, что значение nil
expect(subject.result).not_to be_nil
# be_between - проверяет, что значение находится в диапазоне
expect(subject.result).to be_between(16, 18)
# match - проверяет, что строка соответствует регулярному выражению
expect(subject.result.to_s).to match(/\A17\z/)
# start_with - проверяет, что строка начинается с заданного префикса
expect(subject.result.to_s).to start_with('17')
# end_with - проверяет, что строка заканчивается заданным суффиксом
expect(subject.result.to_s).to end_with('17')
# include - проверяет, что массив или строка содержит элемент
expect([17]).to include(subject.result)
# contain_exactly - проверяет, что массив содержит ровно указанные элементы
expect([17]).to contain_exactly(subject.result)
# have_attributes - проверяет, что объект имеет указанные атрибуты
expect(subject).to have_attributes(result: 17)
# respond_to - проверяет, что объект отвечает на указанный метод
expect(subject).to respond_to(:result)
# raise_error - проверяет, что вызов метода вызывает исключение
expect { raise StandardError }.to raise_error(StandardError)
# change - проверяет, что вызов метода изменяет состояние объекта
expect { subject.add(0, 0) }.to change { subject.result }.from(17).to(17)
# satisfy - проверяет, что значение удовлетворяет произвольному условию
expect(subject.result).to satisfy { |value| value == 17 }
end
end
end
Если нам нужно протестировать приватный метод пользуемся send
class Calculator
private
def private_method
"This is a private method"
end
end
RSpec.describe Calculator do
describe "private methods" do
it "tests the private method" do
# Вызываем приватный метод через send
expect(subject.send(:private_method)).to eq("This is a private method")
end
end
end
eq - проверяет на равенство. Матчер, далее рассмотрим подробнее
В данном примере instance_variable_set используется для демонстрации, как можно напрямую устанавливать переменные экземпляра в тестах. В реальных сценариях это может быть полезно, если вы хотите протестировать метод, который зависит от переменной экземпляра, но не хотите или не можете вызвать метод, который её устанавливает. на примере нашего калькулятора:
class Calculator
def initialize(initial_value = 0)
@result = initial_value
end
def add(a, b)
@result += a + b
end
def multiply_result_by(factor)
@result *= factor
end
def result
@result
end
end
И тест для него:
RSpec.describe Calculator do
describe "#add" do
it "returns the sum of two numbers and multiplies the result by a factor" do
calculator = Calculator.new
# Устанавливаем начальное значение переменной экземпляра @result
calculator.instance_variable_set(:@result, 5)
# Вызываем метод add, который изменяет @result, добавляя к нему сумму 3 и 4
calculator.add(3, 4)
# Проверяем, что @result теперь равен 12 (5 + 3 + 4)
expect(calculator.result).to eq(12)
# Вызываем метод multiply_result_by, который умножает @result на заданный фактор
calculator.multiply_result_by(2)
# Проверяем, что @result теперь равен 24 (12 * 2)
expect(calculator.result).to eq(24)
end
end
end
часто не догонял откуда что береться в спеках, тот же subject
В этом случае subject будет экземпляром тестируемого класса.
RSpec.describe ::ТестируемыйКласс do
let(:переменная) { create(:фабрика, которая создает объекты для тестов)
но можем прям задать что будет в subject
subject { ::ТестируемыйКласс.new }
ну а дальше распространенный пример с калькулятором
RSpec.describe Calculator do
subject { Calculator.new }
let(:number1) { 5 }
let(:number2) { 10 }
describe "#add" do
it "adds two numbers" do
result = subject.add(number1, number2)
expect(result).to eq(15)
end
end
describe "#subtract" do
it "subtracts the second number from the first" do
result = subject.subtract(number1, number2)
expect(result).to eq(-5)
end
end
end
например было:
def gear_inches
ratio * Wheel.new(rim, tire).diameter
end
стало:
def gear_inches
ratio * wheel.diameter
end
где создание экземпляра класса вынесли
# Gear нужна «утка», знающая diameter
Gear.new(52, 11, Wheel.new(26, 1.5)).gear_inches
но это прям идеально, поэтому переходим к изоляции зависимостей.
код тут
echo "привет мое любимое php";