Antworten:
Der rad Weg =>
let(:dummy_class) { Class.new { include ModuleToBeTested } }
Alternativ können Sie die Testklasse mit Ihrem Modul erweitern:
let(:dummy_class) { Class.new { extend ModuleToBeTested } }
Die Verwendung von 'let' ist besser als die Verwendung einer Instanzvariablen zum Definieren der Dummy-Klasse in der vorherigen (: each)
let(:dummy_class) { Class.new { include ModuleToBeTested } }
let(:class_instance) { (Class.new { include Super::Duper::Module }).new }
diese Weise erhalte ich die Instanzvariable, die am häufigsten zum Testen verwendet wird.
include
funktioniert nicht für mich, aber extend
funktioniertlet(:dummy_class) { Class.new { extend ModuleToBeTested } }
subject(:instance) { Class.new.include(described_class).new }
Was Mike gesagt hat. Hier ist ein triviales Beispiel:
Modulcode ...
module Say
def hello
"hello"
end
end
Spezifikationsfragment ...
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
Gibt es einen Grund, warum Sie nicht in der DummyClass-Deklaration waren, anstatt anzurufen extend
?
extend
in die Instanz der Klasse ein, dh nachdem new
er gerufen wurde. Wenn Sie dies getan haben, bevor new
aufgerufen wird, dann haben Sie Recht, Sie würden verwendeninclude
DummyClass
Konstante definieren ? Warum nicht einfach @dummy_class = Class.new
? Jetzt verschmutzen Sie Ihre Testumgebung mit einer unnötigen Klassendefinition. Diese DummyClass ist für jede einzelne Ihrer Spezifikationen definiert. In der nächsten Spezifikation, in der Sie denselben Ansatz verwenden und die DummyClass-Definition erneut öffnen, enthält sie möglicherweise bereits etwas (obwohl in diesem trivialen Beispiel die Definition im wirklichen Leben streng leer ist Anwendungsfälle Es ist wahrscheinlich, dass irgendwann etwas hinzugefügt wird und dieser Ansatz dann gefährlich wird.)
Für Module, die isoliert oder durch Verspotten der Klasse getestet werden können, gefällt mir Folgendes:
Modul:
module MyModule
def hallo
"hallo"
end
end
spec:
describe MyModule do
include MyModule
it { hallo.should == "hallo" }
end
Es mag falsch erscheinen, verschachtelte Beispielgruppen zu entführen, aber ich mag die Knappheit. Irgendwelche Gedanken?
let
von @metakungfu beschriebene Methode ist besser.
Ich habe eine bessere Lösung auf der rspec-Homepage gefunden. Anscheinend unterstützt es gemeinsame Beispielgruppen. Von https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !
Gemeinsame Beispielgruppen
Sie können freigegebene Beispielgruppen erstellen und diese Gruppen in andere Gruppen aufnehmen.
Angenommen, Sie haben ein Verhalten, das für alle Editionen Ihres Produkts gilt, sowohl für große als auch für kleine.
Berücksichtigen Sie zunächst das „gemeinsame“ Verhalten:
shared_examples_for "all editions" do
it "should behave like all editions" do
end
end
Wenn Sie dann das Verhalten für die großen und kleinen Editionen definieren müssen, verweisen Sie mit der Methode it_should_behave_like () auf das gemeinsame Verhalten.
describe "SmallEdition" do
it_should_behave_like "all editions"
it "should also behave like a small edition" do
end
end
Könnten Sie auf den ersten Blick eine Dummy-Klasse in Ihrem Testskript erstellen und das Modul in dieses aufnehmen? Testen Sie dann, ob die Dummy-Klasse das erwartete Verhalten aufweist.
BEARBEITEN: Wenn das Modul, wie in den Kommentaren erwähnt, erwartet, dass einige Verhaltensweisen in der Klasse vorhanden sind, in die es gemischt ist, würde ich versuchen, Dummies dieser Verhaltensweisen zu implementieren. Gerade genug, um das Modul glücklich zu machen, seine Aufgaben zu erfüllen.
Trotzdem wäre ich etwas nervös wegen meines Designs, wenn ein Modul eine ganze Menge von seiner Host-Klasse erwartet (sagen wir "Host"?) - Wenn ich nicht bereits von einer Basisklasse erbe oder nicht injizieren kann Die neue Funktionalität in den Vererbungsbaum, dann würde ich versuchen, solche Erwartungen, die ein Modul haben könnte, zu minimieren. Ich befürchte, dass mein Design einige Bereiche unangenehmer Inflexibilität entwickeln könnte.
Die akzeptierte Antwort ist meiner Meinung nach die richtige Antwort. Ich wollte jedoch ein Beispiel für die Verwendung von rpsecs shared_examples_for
und it_behaves_like
Methoden hinzufügen . Ich erwähne einige Tricks im Code-Snippet, aber für weitere Informationen siehe diese relishapp-rspec-Anleitung .
Damit können Sie Ihr Modul in jeder der Klassen testen, die es enthalten. Sie testen also wirklich, was Sie in Ihrer Anwendung verwenden.
Sehen wir uns ein Beispiel an:
# 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
Jetzt erstellen wir eine Spezifikation für unser Modul: 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
Wie wäre es mit:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end
Ich würde vorschlagen, dass man sich für größere und häufig verwendete Module für die "Shared Example Groups" entscheiden sollte, wie von @Andrius hier vorgeschlagen . Für einfache Dinge, für die Sie nicht die Mühe haben möchten, mehrere Dateien usw. zu haben, erfahren Sie hier, wie Sie die maximale Kontrolle über die Sichtbarkeit Ihrer Dummy-Sachen gewährleisten (getestet mit rspec 2.14.6, kopieren Sie einfach den Code und fügen Sie ihn in ein ein Spezifikationsdatei und führen Sie es aus):
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 }
funktioniert nur. Der Fall mit subject { dummy_class }
funktioniert bei mir nicht.
meine jüngste Arbeit mit so wenig Kabel wie möglich
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
Ich wünsche
subject {Class.new{include described_class}.new}
hat funktioniert, aber nicht (wie bei Ruby MRI 2.2.3 und 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>
Offensichtlich ist die beschriebene Klasse in diesem Bereich nicht sichtbar.
Verwenden Sie zum Testen Ihres Moduls:
describe MyCoolModule do
subject(:my_instance) { Class.new.extend(described_class) }
# examples
end
Um einige Dinge, die Sie in mehreren Spezifikationen verwenden, auszutrocknen, können Sie einen gemeinsamen Kontext verwenden:
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
Ressourcen:
Sie können auch den Hilfstyp verwenden
# 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
Hier ist die Dokumentation: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec
Sie müssen Ihr Modul einfach in Ihre Spezifikationsdatei
mudule Test
module MyModule
def test
'test'
end
end
end
in Ihre Spezifikationsdatei aufnehmen
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
Eine mögliche Lösung zum Testen von Modulmethoden, die unabhängig von der Klasse sind, in der sie enthalten sind
module moduleToTest
def method_to_test
'value'
end
end
Und spezifizieren Sie dafür
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
Und wenn Sie sie DRY testen möchten, ist shared_examples ein guter Ansatz
subject(:module_to_test_instance) { Class.new.include(described_class) }
. Ansonsten sehe ich mit Ihrer Antwort nichts falsches.
Dies ist ein wiederkehrendes Muster, da Sie mehr als ein Modul testen müssen. Aus diesem Grund ist es mehr als wünschenswert, einen Helfer dafür zu erstellen.
Ich habe diesen Beitrag gefunden , in dem erklärt wird, wie es geht, aber ich komme hier zurecht, da die Site möglicherweise irgendwann heruntergefahren wird.
Um zu vermeiden, dass die Objektinstanzen die Instanzmethode nicht implementieren :: welcher Fehler auch immer auftritt, wenn Sie versuchen, allow
Methoden für die dummy
Klasse zu verwenden.
Im 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
Im 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
In Ihren Spezifikationen:
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