Ответы:
Рад пути =>
let(:dummy_class) { Class.new { include ModuleToBeTested } }
В качестве альтернативы вы можете расширить тестовый класс с помощью вашего модуля:
let(:dummy_class) { Class.new { extend ModuleToBeTested } }
Использование 'let' лучше, чем использование переменной экземпляра для определения фиктивного класса в before (: each)
let(:dummy_class) { Class.new { include ModuleToBeTested } }
let(:class_instance) { (Class.new { include Super::Duper::Module }).new }
таким образом я получаю переменную экземпляра, которая чаще всего используется для тестирования любым способом.
include
не работает для меня, но extend
делаетlet(:dummy_class) { Class.new { extend ModuleToBeTested } }
subject(:instance) { Class.new.include(described_class).new }
Что сказал Майк Вот тривиальный пример:
код модуля ...
module Say
def hello
"hello"
end
end
фрагмент спец ...
class DummyClass
end
before(:each) do
@dummy_class = DummyClass.new
@dummy_class.extend(Say)
end
it "get hello string" do
expect(@dummy_class.hello).to eq "hello"
end
include Say
указали внутри объявления DummyClass вместо вызова extend
?
extend
в экземпляр класса, то есть после того, new
как был вызван. Если бы вы делали это до того, new
как вас вызвали, значит, вы правы, вы бы использовалиinclude
DummyClass
константу? Почему не просто @dummy_class = Class.new
? Теперь вы загрязняете свою тестовую среду ненужным определением класса. Этот класс DummyClass определен для каждой из ваших спецификаций, и в следующей спецификации вы решите использовать тот же подход и заново открыть определение DummyClass, которое может уже содержать что-то (хотя в этом тривиальном примере это определение является абсолютно пустым в реальной жизни Примеры использования, вероятно, что-то будет добавлено в какой-то момент, и тогда этот подход станет опасным.)
Для модулей, которые могут быть протестированы изолированно или с помощью насмешек над классом, мне нравится что-то вроде:
модуль:
module MyModule
def hallo
"hallo"
end
end
спецификация:
describe MyModule do
include MyModule
it { hallo.should == "hallo" }
end
Может показаться неправильным захватывать вложенные группы примеров, но мне нравится краткость. Есть предположения?
let
лучше использовать метод, описанный @metakungfu.
Я нашел лучшее решение на домашней странице rspec. По-видимому, он поддерживает общие группы примеров. С https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !
Общие примеры групп
Вы можете создавать общие группы примеров и включать эти группы в другие группы.
Предположим, у вас есть поведение, которое применимо ко всем выпускам вашего продукта, как большим, так и маленьким.
Во-первых, выделите «общее» поведение:
shared_examples_for "all editions" do
it "should behave like all editions" do
end
end
затем, когда вам нужно определить поведение для больших и малых выпусков, обратитесь к общему поведению, используя метод it_should_behave_like ().
describe "SmallEdition" do
it_should_behave_like "all editions"
it "should also behave like a small edition" do
end
end
Сверх того, не могли бы вы создать в своем тестовом скрипте фиктивный класс и включить в него модуль? Затем проверьте, что фиктивный класс ведет себя так, как вы ожидаете.
РЕДАКТИРОВАТЬ: Если, как указано в комментариях, модуль ожидает, что некоторые поведения будут присутствовать в классе, в который он смешан, то я бы попытался реализовать макеты этих поведений. Достаточно, чтобы модуль с радостью выполнял свои обязанности.
Тем не менее, я буду немного нервничать по поводу своего дизайна, когда модуль ожидает многого от своего хоста (мы говорим «хост»?) Класса - если я еще не наследую от базового класса или не могу внедрить новая функциональность в дереве наследования, то я думаю, что я буду пытаться минимизировать любые такие ожидания, которые может иметь модуль. Меня беспокоит то, что мой дизайн начнет развиваться в некоторых областях с неприятной негибкостью.
Я думаю, что принятый ответ является правильным, однако я хотел бы добавить пример использования rpsecs shared_examples_for
и it_behaves_like
методов. Я упоминаю несколько трюков в фрагменте кода, но для получения дополнительной информации см. Этот relishapp-rspec-guide .
При этом вы можете протестировать свой модуль в любом из классов, которые его включают. Таким образом, вы действительно тестируете то, что используете в своем приложении.
Давайте посмотрим на пример:
# Lets assume a Movable module
module Movable
def self.movable_class?
true
end
def has_feets?
true
end
end
# Include Movable into Person and Animal
class Person < ActiveRecord::Base
include Movable
end
class Animal < ActiveRecord::Base
include Movable
end
Теперь давайте создадим спецификацию для нашего модуля: movable_spec.rb
shared_examples_for Movable do
context 'with an instance' do
before(:each) do
# described_class points on the class, if you need an instance of it:
@obj = described_class.new
# or you can use a parameter see below Animal test
@obj = obj if obj.present?
end
it 'should have feets' do
@obj.has_feets?.should be_true
end
end
context 'class methods' do
it 'should be a movable class' do
described_class.movable_class?.should be_true
end
end
end
# Now list every model in your app to test them properly
describe Person do
it_behaves_like Movable
end
describe Animal do
it_behaves_like Movable do
let(:obj) { Animal.new({ :name => 'capybara' }) }
end
end
Что о:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end
Я хотел бы предложить, чтобы для более крупных и часто используемых модулей был выбран «Shared Example Groups», как предложено здесь @Andrius . Для простых вещей, для которых вы не хотите испытывать трудности с наличием нескольких файлов и т. Д., Вот как обеспечить максимальный контроль над видимостью ваших пустышек (протестировано с помощью rspec 2.14.6, просто скопируйте и вставьте код в spec-файл и запустите его):
module YourCoolModule
def your_cool_module_method
end
end
describe YourCoolModule do
context "cntxt1" do
let(:dummy_class) do
Class.new do
include YourCoolModule
#Say, how your module works might depend on the return value of to_s for
#the extending instances and you want to test this. You could of course
#just mock/stub, but since you so conveniently have the class def here
#you might be tempted to use it?
def to_s
"dummy"
end
#In case your module would happen to depend on the class having a name
#you can simulate that behaviour easily.
def self.name
"DummyClass"
end
end
end
context "instances" do
subject { dummy_class.new }
it { subject.should be_an_instance_of(dummy_class) }
it { should respond_to(:your_cool_module_method)}
it { should be_a(YourCoolModule) }
its (:to_s) { should eq("dummy") }
end
context "classes" do
subject { dummy_class }
it { should be_an_instance_of(Class) }
it { defined?(DummyClass).should be_nil }
its (:name) { should eq("DummyClass") }
end
end
context "cntxt2" do
it "should not be possible to access let methods from anohter context" do
defined?(dummy_class).should be_nil
end
end
it "should not be possible to access let methods from a child context" do
defined?(dummy_class).should be_nil
end
end
#You could also try to benefit from implicit subject using the descbie
#method in conjunction with local variables. You may want to scope your local
#variables. You can't use context here, because that can only be done inside
#a describe block, however you can use Porc.new and call it immediately or a
#describe blocks inside a describe block.
#Proc.new do
describe "YourCoolModule" do #But you mustn't refer to the module by the
#constant itself, because if you do, it seems you can't reset what your
#describing in inner scopes, so don't forget the quotes.
dummy_class = Class.new { include YourCoolModule }
#Now we can benefit from the implicit subject (being an instance of the
#class whenever we are describing a class) and just..
describe dummy_class do
it { should respond_to(:your_cool_module_method) }
it { should_not be_an_instance_of(Class) }
it { should be_an_instance_of(dummy_class) }
it { should be_a(YourCoolModule) }
end
describe Object do
it { should_not respond_to(:your_cool_module_method) }
it { should_not be_an_instance_of(Class) }
it { should_not be_an_instance_of(dummy_class) }
it { should be_an_instance_of(Object) }
it { should_not be_a(YourCoolModule) }
end
#end.call
end
#In this simple case there's necessarily no need for a variable at all..
describe Class.new { include YourCoolModule } do
it { should respond_to(:your_cool_module_method) }
it { should_not be_a(Class) }
it { should be_a(YourCoolModule) }
end
describe "dummy_class not defined" do
it { defined?(dummy_class).should be_nil }
end
subject { dummy_class.new }
работает. Случай с subject { dummy_class }
не работает для меня.
моя недавняя работа, используя как можно меньше жесткой проводки
require 'spec_helper'
describe Module::UnderTest do
subject {Object.new.extend(described_class)}
context '.module_method' do
it {is_expected.to respond_to(:module_method)}
# etc etc
end
end
я желаю
subject {Class.new{include described_class}.new}
работал, но это не так (как в Ruby MRI 2.2.3 и RSpec :: Core 3.3.0)
Failure/Error: subject {Class.new{include described_class}.new}
NameError:
undefined local variable or method `described_class' for #<Class:0x000000063a6708>
Очевидно, описанный_класс не виден в этой области.
Чтобы проверить свой модуль, используйте:
describe MyCoolModule do
subject(:my_instance) { Class.new.extend(described_class) }
# examples
end
Чтобы высушить некоторые вещи, которые вы используете в нескольких спецификациях, вы можете использовать общий контекст:
RSpec.shared_context 'some shared context' do
let(:reused_thing) { create :the_thing }
let(:reused_other_thing) { create :the_thing }
shared_examples_for 'the stuff' do
it { ... }
it { ... }
end
end
require 'some_shared_context'
describe MyCoolClass do
include_context 'some shared context'
it_behaves_like 'the stuff'
it_behaves_like 'the stuff' do
let(:reused_thing) { create :overrides_the_thing_in_shared_context }
end
end
Ресурсы:
Вы также можете использовать тип помощника
# api_helper.rb
module Api
def my_meth
10
end
end
# spec/api_spec.rb
require "api_helper"
RSpec.describe Api, :type => :helper do
describe "#my_meth" do
it { expect( helper.my_meth ).to eq 10 }
end
end
Вот документация: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec
вам нужно просто включить ваш модуль в ваш файл спецификаций
mudule Test
module MyModule
def test
'test'
end
end
end
в вашем файле спецификаций
RSpec.describe Test::MyModule do
include Test::MyModule #you can call directly the method *test*
it 'returns test' do
expect(test).to eql('test')
end
end
Одно возможное решение для тестирования метода модуля, который не зависит от класса, который будет включать их
module moduleToTest
def method_to_test
'value'
end
end
И спец для него
describe moduleToTest do
let(:dummy_class) { Class.new { include moduleToTest } }
let(:subject) { dummy_class.new }
describe '#method_to_test' do
it 'returns value' do
expect(subject.method_to_test).to eq('value')
end
end
end
И если вы хотите DRY их протестировать, то shared_examples - хороший подход.
subject(:module_to_test_instance) { Class.new.include(described_class) }
. В противном случае я не вижу ничего плохого в вашем ответе.
Это повторяющийся шаблон, так как вам нужно протестировать более одного модуля. По этой причине более чем желательно создать помощника для этого.
Я нашел этот пост, который объясняет, как это сделать, но я справляюсь здесь, так как сайт может быть закрыт в какой-то момент.
Это сделано для того, чтобы экземпляры объекта не реализовывали метод экземпляра: любую ошибку, которую вы получаете при попытке использовать allow
методыdummy
классе.
В spec/support/helpers/dummy_class_helpers.rb
module DummyClassHelpers
def dummy_class(name, &block)
let(name.to_s.underscore) do
klass = Class.new(&block)
self.class.const_set name.to_s.classify, klass
end
end
end
В spec/spec_helper.rb
# skip this if you want to manually require
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
RSpec.configure do |config|
config.extend DummyClassHelpers
end
В ваших спецификациях:
require 'spec_helper'
RSpec.shared_examples "JsonSerializerConcern" do
dummy_class(:dummy)
dummy_class(:dummy_serializer) do
def self.represent(object)
end
end
describe "#serialize_collection" do
it "wraps a record in a serializer" do
expect(dummy_serializer).to receive(:represent).with(an_instance_of(dummy)).exactly(3).times
subject.serialize_collection [dummy.new, dummy.new, dummy.new]
end
end
end