RSpec Tutorial: Введение

сентября 18, 2011  |  Published in BDD, RSpec, Тестирование  |  14 Comments

UPD: Добавлен параграф об around() хуке.

toolkit rspecДоброго времени суток ув. читатель RubyDev’а!
В данный момент автор RubyDev, то есть я, очень похож на белку в колесе ибо он занимается устройством на работу. К сожалению или к великому счастью обязательным условием является умение работать с RSpec, которым автор владеет в совершенстве плохо, по сему, решил я бросить все ресурсы на то, чтобы изучить сие твоение Девида Челимски.

Данную статья пишу я не от того, что вдруг появилось свободное время, но потому, что это единственный способ для меня самого хорошо освоить материал, текст статьи я как-бы пишу сам для себя, объясняя себе то, что только что прочитал в том малом, что мне удалось, словно золото из золотой жилы, выбить да вышкребать, в интернетах.

Ибо время — деньги, а платить мне никто за это старание не будет, буду совсем краток.

RSpec — это фреймворк для тестирования написанный на Ruby и предоставляющий специальный DSL (вы уже должны знать, что это) для написания тестов — спецификаций. RSpec — инструмент для BDD. BDD (Bihavior-Driven Development) — разработка с опережающим описанием поведения программы. Другими словами сначала мы описываем в спецификации то, как должна себя вести программа, а затем пишем саму программу. Спецификации при этом используются не просто как описание «Вот эта штука должна напечатать: Hello, World!», но и как тест, проверка того, что программа выведет именно «Hello, World!», а не что-то похабное и нецензурное.

В этой статейке или статеночке мы сконцентрируемся на работе с самим RSpec и совсем не будем затрагивать различные вспомогательные штуки-дрюки типа factory-girl и т.д. ибо я сам еще не сильно во всем разобрался.

Итак, начинаем с установки:

$ gem install rspec
Fetching: rspec-core-2.6.4.gem (100%)
Fetching: diff-lcs-1.1.3.gem (100%)
Fetching: rspec-expectations-2.6.0.gem (100%)
Fetching: rspec-mocks-2.6.0.gem (100%)
Fetching: rspec-2.6.0.gem (100%)
Successfully installed rspec-core-2.6.4
Successfully installed diff-lcs-1.1.3
Successfully installed rspec-expectations-2.6.0
Successfully installed rspec-mocks-2.6.0
Successfully installed rspec-2.6.0
5 gems installed

Кратко о том, что мы установили:
rspec-core — собственно ядро RSpec

rspec-expectations — добавляет expectations(далее по тексту просто «ожидания») и matchers(назовем из «матчеры»).

rspec-mocks — добавляет возможность делать mocking и stubbing (назовем «мокинг» и «стубинг»).

Теперь расскажу кратко о том, что такое метчеры и ожидания.

Ожидания — это методы should и should_not, которые добавляются к некоторому объекту поведение которого мы описываем.

Матчер используется описания и проверки поведения объэкта.

Пример:

Строка «RubyDev» должна содержать 7 символов.

Здесь «RubyDev» — тестируемый объект, должна — ожидание, содержать 7 символов — матчер.

Другой пример:

Строка «RubyDev» не должна содержать 5 символов.

Здесь ожиданием будет «не должна».

А теперь посмотрим на то, как это все будет выглядеть если мы используем RSpec:

string = "RubyDev"
string.should have(7).characters
string.should_not have(5).characters

Теперь следует увеличить масштам и взглянуть на спецификацию целиком!

Спецификация (spec) — это, как правило, отдельный файл, который содержит описание какой-нибудь части программы, в контексте Rails, это может быть описание целого контроллера, модели, шаблона, партиала, хелпера и т.д. Файлы спецификаций принято хранить в поддиректории spec проекта, а имена файлов должны заканчиваться на _spec.rb.

Сейчас мы не будем заострять внимание на написании спеков для Rails-приложения ибо это уже несколько отдельная тема. Вместо этого мы быстро и стремительно на нашем рубиновом дельтоплане пролетим по азам работы с RSpec.

Давайте создадим простое приложение, которое познакомит нас с ожиданиями и матчерами. Создадим директорию проекта proj1 и два файла user.rb и account.rb.

#user.rb
require './account'

class User
end
#account
require './user'

class Account
end

Теперь мы должны создать директорию spec в нашем проекте, где будем хранить спецификаци (спеки), а в ней две спецификации: user_spec.rb и account_spec.rb.

Наше простое приложение — какая-то система интернет-банкинга. Пользователь может иметь один аккаунт, а аккаунт может принадлежать только одному пользователю. Давайте опишем это в наших спецификациях:

require "./user.rb"
require "./account.rb"
require 'rspec'

describe User do
  before(:all) do
    @user = User.new("Vasya", "Ivanov", 18)
    @account = @user.account
  end

  it 'has a fullname' do
    @user.fullname.should eq "Vasya" + " " + "Ivanov"
  end

  it 'is older then 18' do
    User.new("Ivan","Ivanov", 10).should raise_error()
  end

  it 'has an account' do
    @account.user.should be @user
    @user.account.should be @account
  end
end
#../spec/account_spec.rb
require "./account.rb"
require "./user.rb"
require 'rspec'

describe Account do
  before(:all) do
    @user = User.new("Vasya", "Ivanov", 18)
    @account = @user.account
  end

  it 'belongs to the user' do
    @account.user.should be(@user)
  end

  it 'has list of accounts' do
    Account.all.should include(@account)
  end

  it "should has an ID" do
    @account.id.should_not be_false
  end

  it "has a balance in the start" do
    @account.balance.should be 0
  end
end

Для запуска спецификаций воспользуемся командой rspec <filepath>:

$ rspec ./spec/user_spec.rb
FFF

Failures:

  1) User has a fullname
     Failure/Error: @user.has_account
     NoMethodError:
       undefined method `has_account’ for #<User:0x8b1d458>
     # ./spec/user_spec.rb:7:in `block (2 levels) in <top (required)>’

  2) User is older then 18
     Failure/Error: @user.has_account
     NoMethodError:
       undefined method `has_account’ for #<User:0x8b1d458>
     # ./spec/user_spec.rb:7:in `block (2 levels) in <top (required)>’

  3) User has an account
     Failure/Error: @user.has_account
     NoMethodError:
       undefined method `has_account’ for #<User:0x8b1d458>
     # ./spec/user_spec.rb:7:in `block (2 levels) in <top (required)>’

Finished in 0.00033 seconds
3 examples, 3 failures

Failed examples:

rspec ./spec/user_spec.rb:11 # User has a fullname
rspec ./spec/user_spec.rb:15 # User is older then 18
rspec ./spec/user_spec.rb:19 # User has an account

Как видите, RSpec сообщает, что код не соответствует спецификации, в нашем случае причиной тому является то, что классы User и Account пусты. Для того, чтобы тестирование прошло успешно, нам необходимо реализовать в классах User и Account то поведение, которое мы описали в спецификациях.

#user.rb
require './account'

class User
  attr_reader :name, :lname, :fullname, :age, :account

  def initialize(name, lname, user_age)
    @name  = name
    @lname = lname
    @fullname = name + " " + lname
    self.age = user_age
    @account = Account.new(self)
  end

  def age=(user_age)
    if user_age >= 18
      @age = user_age
    else
      raise "So young!"
    end
  rescue RuntimeError => error
    return error.message
  end
end
#account.rb
require './user'

class Account
  attr_reader :user, :id
  attr_accessor :balance

  def initialize(user)
    @user = user
    @balance = 0
    self.class.add_account(self)
    @id = self.set_id
  end

  @@accounts = []

  def set_id
    prev_account_id = self.class.all.last.id
    @id = prev_account_id ? prev_account + 1 : 1
  end

  def self.all
    @@accounts
  end

  def self.add_account(account)
    @@accounts << account
  end
end

Теперь, если запустить rspec, то мы увидим, что наше приложние согласно спецификациям работает правильно:

$ rspec ./spec/account_spec.rb
….

Finished in 0.00361 seconds
4 examples, 0 failures

$ rspec ./spec/user_spec.rb

Finished in 0.00174 seconds
3 examples, 0 failures

Теперь, когда мы научились писать простые спеки, необходимо разобраться что же мы в них писали =)

describe … do
  …
end

или

context … do
  …
end

- это синонимы. В этих блоках определяется описание какой-нибудь функциональной части приложения. Например:

describe ArticlesController do
  describe «#index» do
    …
  end

  …
end

Как видите, эти блоки могут быть вложенными, глубина вложенности может быть абсолютно любая.

Следующее, что нам следует разобрать — это examples или, если перевести на русский — примеры. Примеры — это те самые блоки it … { … }. Каждый такой пример описывает поведение какой-нибудь функциональной единицы, например метода. Иногда для описания функции метода или функциональности цепочки методов приходится использовать несколько примеров.

Еще нам следует разобраться с тем, что такое pending examples или отложенные примеры (точнее незаконченные, висящие или те, реализацию которых мы ожидаем в будующем). Мы не использовали эту возможность в примере выше, однако о ней вам необходимо знать уже сейчас. Спецификация не всегда проверяет код, иногда программист нужно добавить какой-то пример для функциональности, которая совсем не скоро будет реализовано, но пример должен быть, в такой способ мы как-бы помечаем то, что нам нужно сделать в будующем. Такие пустые примеры и называются pindings. Простой пример pending:

describe "do something" do
  it "do something"
end

При запуске спецификации в отчете будет записано:

Do something
  do something (PENDING: Not Yet Implemented)

Pending:
  Account Do something do something
    # Not Yet Implemented
    # ./spec/account_spec.rb:4

Finished in 0.00278 seconds
1 examples, 0 failures, 1 pending

Вы также можете использовать специальный метод pending в контексте блока it для описания причины почему пример временно отложен:

describe "Do something" do
  it "do something" do
    pending "Some bugs needs to be fixed."
  end
end

$ rspec spec/account_spec.rb —format documentation —color

Do something
    do something (PENDING: Some bugs needs to be fixed.)

Pending:
  Account Do something do something
    # Some bugs needs to be fixed.
    # ./spec/account_spec.rb:4

Finished in 0.00299 seconds
1 examples, 0 failures, 1 pending

Вы также можете использоовать xit вместо it:

describe "Do something" do
  xit "do something" do
    (1).should be_true
  end
end

Первый вариант хорош когда вам не нужно указавать причины того, почему пример не реализован. Второй, когда необходимо указать, причину того, почему пример находится в ожидании. А третий подходит когда не нужно показывать причину того, что пример отложен и при этом содержимое блока примера готово, однако, из-за некоторых багов или нереализованности каких-то других частей программы, пример не может сработать успешно. Когда баги будут исправлены и нужный функцинал дописан, то вам достаточно просто убрать «х» из «xit» и пример станет рабочим.

В примере выше (в том, где мы писали преложиние с аккаунтами и пользователями) мы использовали следующие матчеры:

be — (obj.should be) срабатывает если  obj true или любое другое значение кроме nil и false. Второй вариант (obj.should be 5) — срабатывает когда obj.equal? 5.

be_false — (obj.should be_false) — срабатывает если obj == nil или obj == false.

include — ([1,2,3].should include 2) — cрабатывает если коллекция содержит элемент, этот матчер похож на метод include?.

raise_error — (obj.do_something.raise_error) — данный матчер срабатывает когда вызывается ошибка, тип ошибки и сообщение можно передать в условиях матчеру.

eq (3.should eq 3) — данный матчер срабатывает, например когда obj1 == obj2.

Но это не все матчеры! Далее мы познакомимся с остальными матчерами, однако прежде мы вспомним различия между .eql? .equal? и ==, это очень важно для того, чтобы вы понимали разницу между некоторыми матчерами и правильно их использовали.

== — проверяет соответствие значений двух объектов:

1.0 == 1 #=> true

.eql? — проверяет соответствие значений двух объектов и их тип:

(1.0).eql? 1 #=> false
(1).eql? 1 #=> true

.equal? — проверяет являются ли A и B одним объектом (имеют одинаковый .object_id):

1.equal? 1 #=> true
:rubydev.equal? :rubydev #=> true
"rubydev".equal? "rubydev" #=> false
obj = "rubydev" #=> "rubydev"
obj2 = obj
obj.equal? obj2 #=> true

Так, с этим разобрались, перейдем к знакомству с остальными матчерами:

Матчеры: equal, eql и == соответствуют методам сравнения описанным выше.

Матчер eq соответствует матчеру ==.

Матчер be(obj) соответствует матчеру equal.

Be-матчеры:
О матчере be и be_false я уже говорил.

be_true — срабатывает если объект конвертируется в true, то есть его значение не nil и не false.

be_nil — срабатывает если значение объекта nil.

be_within(delta).of(value) — срабатывает если значение объекта находится в пределах value +- delta. Пример:

10.should be_within(5).of(11), данный пример сработает так как 10 попадает в диапазон от 7 до 15 включительно.

Матчер be также можно использовать совместно с операторами сравнени, например:

100.should be > 1
100.should be >= 99
100.should be < 101

Матчер expect change очень полезен благодаря тому, что применяется к блоку кода и позволяет оценить изменение состояния объекта. Это может быть полезно, например для проверки правильности работы counter cache при работе с Rails. Пример:

obj = 10
#проверяем, что значение объекта изменилось с 10 на 110
expect{obj += 100}.to change{obj}.from(10).to(110)
#проверяем, что значение объекта увеличилось на 100
expect{obj += 100}.to change{obj}.by(100)

Матчер raise_error или raise_exception срабатывает когда возвращается ошибка. Синтаксис: raise_error(Error, Message). Error — тип ошибки, а Message — сообщение. Без этих параметров матчер будет срабатывать для любой ошибки. Пример:

expect{nil + 1}.to raise_error(NoMethodError)

Have-матчеры используются для проверки количества элементов в коллекции. Существуют следующие have-матчеры:

have (have_exactly) — объект должен иметь точно определенное количество элементов.

have_at_least — объект должен имет количество элементов больше или равно, чем указано.

have_at_most — объект должен иметь количество элементов меньше или равное указанному.

#строка "rubydev" должна иметь 7 символов
"rubydev".should have(7).characters

#строка должна содержать не более 8 каких-то штук
"rubydev".should have_at_most(8).items

#массив должен содержать не менее 2 слов
["hello","bye"].should have_at_least(2).words

#массив должен иметь не менее 1 элемента
["hello","bye"].should have_at_least(1).items

Приставка после have, может быть, почти любой, какую вы пожелаете, только я бы советовал вам добавлять осмысленные приставки (items, words, elements и т.д.).

Матчер include мы уже рассматривали выше, однако следует рассмотреть его более подробно. Ниже приведены примеры объясняющие работу матчера include:

"chicken".should include("c")
[1,2,3].should include(1)
[1,2,3].should include(1,2,3)
hash = {:name => "Vasya", :lname => "Petrov"}
hash.should include(:name)
hash.should include(:name => "Vasya")
hash.should_not include(:name => "Vova")

Матчер match (простите за тафтологию) используется для проверки соответствия значения объекта шаблону регулярных выражений. Пример:

"James Bond".should match "Bond"
"James Bond".should match /Bond/
"James Bond".should =~ /Bond/
"James Bond".should match "(Bond)$"
"James Bond".should_not match /^(Bond)/

Матчер =~ является синонимом для матчера match, однако может также использоваться для сравнения массивов:

[1,2,3].should =~ [1,2,3]
[1,2,3].should =~ [3,2,1]
[1,2,3].should_not =~ [1,2]

RSpec автоматически создает матчеры для методов, имена которых заканчиваются символом «?», то есть таких, что возвращают булево значение. Мы можем делать так:

5.zero?.should be_false

Однако этот код не соответстует философии RSpec согластно которой язык описания спецификаций должен быть максимально похож на человеческий. Таким образом RSpec предоставляет нам специальные матчеры для написания читабельных спеков:

5.should_not be_zero
[].should be_empty

class A
  def self.good?
    true
  end
end

A.should be_good

Матчер respond_to используется для проверки того, отвечает ли объект на вызов метода для него, то есть, содержит ли объект определенный метод. Пример:

class A
  def self.good?
    true
  end
end
A.should respond_to("good?")
"rubydev".should respond_to(:upcase,:downcase)

Матчер satisfy — очень гибок и позволяет проверить практически все, что угодно. satisfy передает объект в блок, где над объектом выполняются определенные действия, если блог возвращает true, то матчер проходит, если false, то не проходит. Примеры:

"rubydev".should satisfy{|word| word.length > 5 }
{:q=>10}.should satisfy{|h| h.has_key?(:q)}
[1,2,3].should satisfy{|arr| arr.should include 2 }

Матчер throw_symbol используется для проверки правильности работы с ошибками, а точнее с catch и throw, а еще точнее с throw, с тем, какой блок кода исполняется для обработки ошибки (блоки именуются при помощи символов, при определенном условии throw выполняет определенный блок кода, который определен через catch(:symbol)):

expect{throw :foo}.to throw_symbol(:foo)

Матчеры проверяющие тип объекта be_a_kind_of и be_an_instance_of. Примеры:

:symbol.should be_a_kind_of Symbol
"string".should be_an_instance_of String
class A
...
end
A.new.should be_a_kind_of A

Матчер cover используется для проверки вхождения объекта в диапазон, например:

(1..4).should cover(4)
(1..4).should cover(1,2,3,4)
(1...4).should_not cover(4, 10)
('a'..'z').should cover("x")

Вот мы и рассмотрели, наверное, все стандартные матчеры.

Остается узнать что такое before(:all) do … end и т.д.

before и after — это так называемые хуки, которые позволяют избегать повторения в коде примеров и, кроме того, реализовывают инициализацию необходимых объектов. В нашем примере спеков мы можем найти следующий пример использования:

before(:all) do
  @user = User.new("Vasya", "Ivanov", 18)
  @account = @user.account
end

Данный хук выполняется один раз перез запуском всех примеров (параметр :all) и устанавливает объекты @user и @account, которые далее используются в примерах.

Если в параметрах к before передать :each, то блок кода будет выполняться перед каждым примером, то есть столько раз, сколько примеров содержится в спецификации.

Кроме before(:each) или before(:all) имеется также хук after(:each) или after(:all), который выполняется соответственно после каждого примера или после выполнения всех примеров.

Cуществует также around() хук. around принимает блок кода (пример) и позволяют в своем собственном блоке кода определить действий которые будут выполнятся до и после вызова процедуры в которую был преобразован принятый блок кода примера. Пример:

RSpec.configure do |config|
  config.around(:each) do |example|
    @var = 100
    example.call #or run
    @var = 0
  end
end

it("is 100") { @var.should be 100 }

Также вам следует знать о том, что describe и it имеют синонимы: context и specify соответственно.

describe … do
  it … {…}

  context … do
    it … {…}
    specify…{…}
  end
end

Работая с хуками типа before и after вы должны знать, что хуки внешнего описания (контекста) доступны во всех вложенных контекстах не завасимо от глубины вложенности.

Вы также можете оределять фильтры для хуков в конфигурации для спецификации. Фильтры позволяют хукам before и after выполняться только для определенных примеров. Ниже показан пример использования фильтров:

RSpec.configure do |config|
  config.before(:each) do
    @var ||= 0
  end
end

config.before(:each, :turn => :second) do
  @var += 1
end

config.before(:each, :turn => :third) do
  @var += 5
end

it "should be 0", :turn => :first do
  @var.should be 0
end

it("should be 1", :turn => :second) do
  @var.should be 1
end

it "should be 5", :turn => :third do
  @var.should be 5
end

it "should be 1", :turn => :second do
  @var.should be 1
end

Кроме хуков в RSpec присутствуют методы — хэлперы и возможность определять их самостоятельно.

Методы let и let! используются для создания кэшируемых хэлперов в RSpec. let и let! позволяют создать хелперы, которые в контексте каждого примера не изменяются, однако в контексте другого примера могут меняться. Пример:

let(:balance){ @account.balance += 10 }

it "should be 10" do
  balance.should be 10
end

it "should be 20" do
  balance.should be 20
end

Разница между let и let! заключается в том, что хэлпер объявленный через let выполняется только в контексте примеров. Если вам необходимо использовать хэлпер вне примеров, например в before — хуке, то вам следует использовать let!.

Вы также можете создавать методы-хэлперы так же как и обычные методы в Ruby. Пример:

def get_balance
  "You have " + @account.balance.to_s + " dollars."
end

it "should return message dollars." do
  get_balance.should == "You have 20 dollars."
end

Такие хэлперы доступны только в контексте своего описания (describe или context) и во всех вложенных контекстах.

Теперь давайте перейдем к объект спецификации. Эта тема, наверное, должна была быть в начале, но я на свой страх и риск перенес ее в конец.

Каждое описание (контекст) или пример имеют объект, который они опысывают (субъект описания). Когда мы пишем:

describe Account do
  …
end

Мы не просто даем понять, что мы описываем класс Account, но мы еще и создаем экземпляр этого класса (объект типа Account), доступ к которому можно получить через метод subject. Таким образом, в нашем приложении мы можем описывать поведение аккаунта пользователя следующим образом:

describe Account do
  it 'is an instance of Account' do
    subject.should.be_an_instance_of Account
    subect.should be_a_kind_of Account
  end
end

В нашем случае такая спецификация вызовет ошибку, ибо инициализатор должен получить один очень важный параметр — пользователя аккаунта. Таким образом мы должны переписать спеку в такой способ:

describe Account.new(User.new("Vasya", "Pupkin", 23)) do
  it 'is an instance of Account' do
    subject.should be_an_instance_of Account
    subject.should be_a_kind_of Account
  end

  it 'has user with name Vasya' do
    subject.user.name.should == "Vasya"
  end
end

Теперь все заработает отлично.

Такие субъекты спецификации как приведено выше называются в RSpec неявными (или предопределенными), а теперь мы поговорим о явных субъектах (определяемых вручную пользователем). Эти субъекты определяются при помощи метода subject, пример:

describe Account do
  subject { Account.new(User.new("Vasya", "Pupkin", 23)) }

  it 'is an instance of Account' do
    subject.should be_an_instance_of Account
    subject.should be_a_kind_of Account
  end

  it 'has user with name Vasya' do
    subject.user.name.should == "Vasya"
  end
end

subject принимает блок кода в котором и определяется субъект спецификации.

Для работы с субъектами специфиации RSpec предоставляет специальный метод its, который является сокражением для использования метода it. its получает в виде стороки или символа имя метода, который следует вызвать для объекта — субъекта тестирования и блок кода в котором находится матчер. Пример:

#перепишим предыдущий пример с использованием its:
describe Account
  subject { Account.new(User.new("Vasya", "Pupkin", 23)) }

  its(:class) { should equal Account }

  its("user.name") { should == "Vasya" }
end

В случае, когда субъект — массив или хэш, вы можете передавть не только строку или символ, но и массив ключей, например:

describe Account
  #...
  describe 'accounts list' do
    subject { Account.all }
    #.all возвращает коллекцию аккаунтов, здесь мы проверяем, действительно ли первый элемент коллекции является аккаунтом.
    its([0]) { should be_an_instance_of Account }
  end
end

Хочу заметить, что в случае с примерами it, should можно вызывать и без объекта-приемника вызова метода, в этом случае объектом-приемником будет служить явно или неявно определенный субъект спецификации. Пример:

describe Account do
  #...
  subject { Account.new(User.new("Vasya", "Pupkin", 23)) }
  it "is an instance of Account" do
    should be_a_kind_of Account
  end
end

С этим все понятно!

Мы знаем, что описывая класс через describe или context мы получаем ссылку на экземпляр этого класса через метод subject. Что делать, если мы хотим описать поведение самого класса, а не его объекта, использовать константу — имя класса, например Account или User?  — Нет, для этого RSpec предоставляет специальный метод described_class. Пример:

describe Account do
  it "is equal for Account class" do
    described_class.should equal Account
  end
end

Для доступа к объекту примера и его метаданным в контексте примера вы можете использовать метод example:

describe Account do
  it "is equal for Account class" do
    described_class.should equal Account
    example.description.should == "is equal for Account class"
  end
end

Давайте теперь воспользуемся нашими знаниями и перепишем спецификации в такой способ, чтобы они выглядели красивее и понятнее.

#account_spec.rb
require "./account.rb"
require "./user.rb"
require 'rspec'

describe Account do
  before(:all) do
    @user = User.new("Vasya", "Ivanov", 18)
    @account = @user.account
  end

  it 'has list of accounts' do
    described_class.all.should include(@account)
  end

  describe "user account" do
    it 'belongs to the user' do
      @account.user.should be @user
    end

    it "has an ID" do
      @account.id.should_not be_false
    end

    describe 'account balance' do
      let(:balance){ @account.balance += 100 }

      it "has a 0 dollars on balance in the start" do
        @account.balance.should be 0
      end

      it "will be increased when user add money" do
        balance.should eq 100
      end
    end
  end
end

$ rspec spec/account_spec.rb —format documentation

Account
  has list of accounts
  user account
    belongs to the user
    has an ID
    account balance
      has a 0 dollars on balance in the start
      will be increased when user add money

Finished in 0.00288 seconds
5 examples, 0 failures

#user_spec.rb
require "./user.rb"
require "./account.rb"
require 'rspec'

describe User do
  it 'is older then 18' do
    described_class.new("Ivan","Ivanov", 10).should raise_error()
  end

  describe 'user properties and abilities' do
    subject { described_class.new("Vasya", "Ivanov", 18) }

    it 'has a fullname' do
      subject.fullname.should eq "Vasya " + "Ivanov"
    end

    it 'has an account' do
      subject.account.should be
    end
  end
end

$ rspec spec/user_spec.rb —format documentation

User
  is older then 18
  user properties and abilities
    has a fullname
    has an account

Finished in 0.00139 seconds
3 examples, 0 failures

Следующая статья будет более практическая, в ней мы допишем наше приложение и познакомимся с различными вспомогательными инструментами для RSpec типа autotest, и т.д. А также узнаем о том, что такое mocking и stubbing, как настраивать RSpec и т.д.

Лучшая благодарность автору — ваши комментарии! 

Tags: , , ,

Responses

  1. int03e says:

    сентября 18, 2011 at 23:17 (#)

    Продолжайте в том же духе, интересно. Правда без описания дополнительных гемов обойтись трудно, думаю в будущем надо затронуть тему factory_girl/fabrication, faker, capybara…

  2. says:

    сентября 18, 2011 at 23:50 (#)

    Человечище!

  3. says:

    сентября 19, 2011 at 07:52 (#)

    С удовольствием прочел, некоторые вещи стали понятнее для меня. Вот только ошибки в статье не радуют, а их очень много.

    Еще не понятно это:

    describe … do

    end

    или

    content … do

    end

    Так content или context? Если context, то этот момент объясняется в статье 2 раза (Также вам следует знать о том, что describe и it имеют синонимы: context и specify соответственно.).

    Спасибо за статью, очень кстати для меня сейчас (очередной приступ энтузиазма у меня).

  4. says:

    сентября 19, 2011 at 08:30 (#)

    Да, хорошо бы ещё несколько статей на эту тему…. никак не заставлю себя писать тесты. Где-то внутри понимаю, а не опыт предыдущих лет не понимает и противится (-:

  5. admin says:

    сентября 19, 2011 at 12:35 (#)

    int03e, не без того будет. Сейчас как раз разбираюсь с FactoryGirl, но что-то писать еще рано.

    Andy, ошибок много ибо сильно спешу и не владею слепым набором. Правильный вариант — context, content — это моя ошибка.

    none, статей по RSpec будет несколько, просто я еще сам не во всем разобрался. В следующей расскажу о настройках и конфигурации, рассмотрю несколько вспомогательных инструментов, mock, stub, допишу приложение и спеки к нему, ну чисто ради закрепления понимания работы с RSpec. Через одну статью планирую уже писать о спеках для Rails, там же место для FactoryGirl, фикстур и прочего найдется.

    Потом, когда азы опишу буду оттачивать технику, изучать спеки различных проектов и писать уже о том как писать спеки наиболее правильным образом.

  6. says:

    сентября 19, 2011 at 14:53 (#)

    А про «огурец» ожидать статей? (-:
    Если надо, могу подкинуть «The RSpec Book», если ещё нету (-:

  7. admin says:

    сентября 19, 2011 at 17:04 (#)

    none, скорее нет, чем да. Cucumber мне кажется бесполезным, я у кого ни спрошу — никто его не использует и все хаят. Если и панишу что-то то совсем поверхностно.

  8. says:

    сентября 24, 2011 at 00:17 (#)

    Лучше использовать watchr вместо autotest и его аналог autowatchr.

    it ‘has an account’ do
    @account.user.should be @user
    @user.account.should be @account
    end

    вот так писать не надо, одно ожидание-одна проверка, так как под описание попадает вторая строка, а первая гласит что «акаунт имеет юзера». не стоит перепроверять вещи по два раза. первая проверка будет на стороне акаунта.
    имхо проверка таким образом не совсем, то… скорее надо проверить, что класс User имеет асоциацию с классом акаунт. с помощью shoulda это звучить просто should belong_to :account. а вот на чистом rspec наверное сложнее(стоит посмотреть код этого матчера)

    p.s. не «стубинг», а «стАбинг» или заглушка

  9. wildDAlex says:

    апреля 6, 2012 at 12:16 (#)

    have_at_least — объект должен имет количество элементов меньшее, чем указано.
    have_at_most — объект должен иметь больше указанного количество элементов.

    Тут кажется наоборот, причем в примерах у вас все верно.

  10. admin says:

    апреля 9, 2012 at 14:58 (#)

    wildDAlex, спасибо, поправил.

  11. Shaker says:

    июня 18, 2012 at 12:58 (#)

    Спасибо за статью! С неё начал изучение RSpec.

  12. DemitriyDN says:

    февраля 4, 2013 at 10:44 (#)

    Разница между let и let!

    Основная разница на сколько я понимаю состоит в том, что let! вызывается как before(:each) для каждого ‘it’ — т.е. если у тебя в ‘it’ не используется «переменная» которая определена в let! — она все равно будет инициализирована…

    Одним словом — если не строить никаки велосипедов — то лучше использовать let ( а не let! )

    ссылочка:

  13. Yevgeniy says:

    января 22, 2014 at 16:50 (#)

    Очень полезная статья. Спасибо большое! Я сам мануальный тестировщик, пытаюсь разобраться с автоматизацией на руби. Что можете посоветовать для начала. Слышал, что весь язык для автоматизации не обязательно осваивать.

  14. Сергій says:

    января 28, 2014 at 12:48 (#)

    Хороша стаття. Дякую за підбір матеріалу і такий аналіз елементів Rspec.

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.