def test_stub_doesnt_care_to_be_called
foo = Object.new
foo.stubs(:bar)
# and never call foo.bar
end
def test_but_mock_does
foo = Object.new
foo.mocks(:bar)
# and never call foo.bar
end
Первый тест проходит, второй ломается со словами "foo должен был
получить .bar(), а не получил ничего".
Использовать все это можно многими способами, один из основных уроков
- не писать mocks() когда можно писать stubs().
--
Alexey Verkhovsky
CruiseControl.rb [http://cruisecontrolrb.thoughtworks.com]
RubyWorks [http://rubyworks.thoughtworks.com]
Вот как это выглядит у меня в хелперах,
которые я использую с RSpec:
module CommonControllerSpecHelper
# Simplest but most userful signin technique ever
def simple_signin(account = Universe.create_account(8))
controller.stub!(:current_account).
and_return(account)
end
# ...
end
С другой стороны, если вы проверяете
саму аутентификацию, вам, возможно,
стоит сделать expectation на Account.authenticate.
Что-то типа
require File.dirname(__FILE__) + '/../spec_helper'
describe SessionsController, 'serving request to /sessions with POST' do
before(:each) do
end
def do_post_to_create_action(params)
post :create, params
end
def post_by_user_who_passes_authentication
@user = Universe.create_account(10)
Account.should_receive(:authenticate).with("j...@satriani.com",
"stagepass").
at_least(:once).
and_return(@user)
controller.should_receive(:current_account).and_return(@user)
do_post_to_create_action :email => "j...@satriani.com", :password
=> "stagepass"
end
it "should redirect successfully signed in user" do
post_by_user_who_passes_authentication
response.should be_redirect
end
it "should redirect successfully signed in user to new feedback
page" do
post_by_user_who_passes_authentication
response.should redirect_to(new_feedback_message_url)
end
def post_by_user_who_doesnt_pass_authentication
Account.should_receive(:authenticate).with("j...@satriani.com",
"12345").
at_least(:once).
and_return(false)
do_post_to_create_action(:email => "j...@satriani.com", :password
=> "12345")
end
it "should show login page when sign in fails" do
post_by_user_who_doesnt_pass_authentication
response.should be_success
end
it "shoud render sessions/new template when sign in fails" do
post_by_user_who_doesnt_pass_authentication
response.should render_template("sessions/new")
end
end
On 15 нояб. 2007, at 19:16, cris...@gmail.com wrote:
> Тоесть, получается, если я тестирую
> метод и внутри него вызывает
> другой метод, то подделывая обращение
> к нему используется stub? А
> mock, это если бы я передавал в этот
> метод объект, интерфейс которого
> имитирует то, что будет дергать мой
> метод?
>
MK
Не думаю, что 20 строчек кода являются
для кого-то critical competitive advantage.
On 16 нояб. 2007, at 11:05, cris...@gmail.com wrote:
>>
> ...
> # use rr for mock and stub
> it "should test method_that_neet_test" do
> # stub or mock???
> mock(Foo).call_private_method(5) { "fake string" }
> # test part
> Foo.method_that_need_test(5).should ...
> end
> ...
>
> class Foo
> class << self
> def method_that_need_test(par1)
> var = call_private_method(par1)
> ...
> end
>
> private
> def call_private_method(par)
> # some logic, that i won't to fake
> end
> end
> end
MK
Зря добавляете. Это не та техника, которая нужна в повседневной жизни. Да и
вообще, лучше обходиться без тестирования private методов.
Поддерживаю. Тема уже обсуждалась тут.
Аргументы против: тестами должен быть покрыт интерфейс. Т.е. Ты меняешь
внутренную логику работы, а тесты продолжают работать.
Вы забываете самое главное: зачем писать приватный метод и тест для него ?!
Этот метод НИКОМУ не нужен.
Чаще всего, новый код пишется как часть какого-нть public метода, а только
потом, в процессе рефакторинга, - выделяется в private метод. И вот для
выделения его в private метод не нужен никакой новый тест.
А то что Вы говорите - это ситуация, когда есть какой-то очень умный класс с
очень маленьким интерфейсом. Для решения проблемы в таких ситуациях
рекомендую почитать матириалы на тему design for testability.
Чаще всего, лучше сделать чуть более толстый, но хорошо тестируемый интерфейс.
А иногда - даже пересмотреть дизайна этого куска системы и разбить один
плохотестируемый интерфейс на несколько хорошо тестируемых.
# First step, extract objects we describe and their possible states/
actions
describe AssetManager, "calculating path to asset" do
end
describe AssetManager, "updating abnormal asset" do
end
# Second step, specify what behaviour you expect
describe AssetManager, "calculating path to asset" do
it "should use root path"
it "should calculate path using id"
end
describe AssetManager, "updating abnormal asset" do
it "should update asset content"
it "should update md5 checksum"
end
# Step three, spec-run-implement-run-refactor-...
# We cannot instantiate modules so lets get away with the following
class AssetManagerStubClass
include AssetManager
end
describe AssetManager, "calculating path to asset" do
it "should use root path" do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
# We prohibit one assertion per test here but this drives us to
accessor implementation
# and makes no harm (side effects) cause it is a simple unit test
#
# Please do not make multiple assertions in controller specs
@asset_manager.root_path.should == "~/dev/playground/ruby/specs/
asset_manager"
# Make sure it actually calculates path relatively to given root
directory
@asset_manager.path(2).should =~ /~\/dev\/playground\/ruby\/specs
\/asset_manager/
end
it "should calculate path using id" do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
@asset_manager.path(4).should == "~/dev/playground/ruby/specs/
asset_manager/assets/1"
end
end
# Then remove duplication...
describe AssetManager, "calculating path to asset" do
before(:each) do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
end
it "should use root path" do
# We prohibit one assertion per test here but this drives us to
accessor implementation
# and makes no harm (side effects) cause it is a simple unit test
#
# Please do not make multiple assertions in controller specs
@asset_manager.root_path.should == "~/dev/playground/ruby/specs/
asset_manager"
# Make sure it actually calculates path relatively to given root
directory
@asset_manager.path(2).should =~ /~\/dev\/playground\/ruby\/specs
\/asset_manager/
end
it "should calculate path using id" do
@asset_manager.path(4).should == "~/dev/playground/ruby/specs/
asset_manager/assets/1"
end
end
# Next behaviour
describe AssetManager, "updating abnormal asset" do
before(:each) do
@io_string = <<-SONG
When I find myself in trouble mother Mary comes to me
Speaking words of wisdom
Let it be
SONG
end
it "should update asset content" do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
id = 3
lambda {
@asset_manager.update_asset(:abnormal, id,
StringIO.new(@io_string))
}.should change(File.new(@asset_manager.path(id)), :mtime)
end
it "should update asset content (implementation specific spec)" do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
@file_instance = mock("file")
@file_instance.should_receive(:write).with(@io_string) # we do
not care what it returns in this case so nil is fine
File.should_receive(:open).
with(@asset_manager.path(id)).
and_yield(@file_instance)
@asset_manager.update_asset(:abnormal, id,
StringIO.new(@io_string))
end
it "should update md5 checksum" do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
id = 3
lambda {
@asset_manager.update_asset(:abnormal, id,
StringIO.new(@io_string))
}.should change(@asset_manager, :md5)
end
end
# Clean it up
describe AssetManager, "updating abnormal asset" do
before(:each) do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
@io_string = <<-SONG
When I find myself in trouble mother Mary comes to me
Speaking words of wisdom
Let it be
SONG
end
def id
3
end
it "should update asset content" do
lambda {
@asset_manager.update_asset(:abnormal, id,
StringIO.new(@io_string))
}.should change(File.new(@asset_manager.path(id)), :mtime)
end
it "should update asset content (implementation specific spec, so
stinky I can't stand it)" do
@asset_manager = AssetManagerStubClass.new(:root => "~/dev/
playground/ruby/specs/asset_manager")
@file_instance = mock("file")
@file_instance.should_receive(:write).with(@io_string) # we do
not care what it returns in this case so nil is fine
File.should_receive(:open).
with(@asset_manager.path(id)).
and_yield(@file_instance)
@asset_manager.update_asset(:abnormal, id,
StringIO.new(@io_string))
end
it "should update md5 checksum" do
lambda {
@asset_manager.update_asset(:abnormal, id,
StringIO.new(@io_string))
}.should change(@asset_manager, :md5)
end
end
MK