wake-up-neo.net

Testen von Modulen in rspec

Was sind die bewährten Methoden zum Testen von Modulen in rspec? Ich habe einige Module, die in wenigen Modellen enthalten sind, und für jetzt habe ich einfach doppelte Tests für jedes Modell (mit wenigen Unterschieden). Gibt es eine Möglichkeit, DRY es aufzubauen?

160
Andrius

Ich habe auf der Homepage von rspec eine bessere Lösung 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 gemeinsam genutzte Beispielgruppen erstellen und schließen Sie diese Gruppen in andere .__ ein. Gruppen.

Angenommen, Sie haben ein Verhalten, das gilt für alle Editionen Ihres Produkt, sowohl groß als auch klein.

Zuerst müssen Sie das "shared" auswerten Verhalten: 

shared_examples_for "all editions" do   
  it "should behave like all editions" do   
  end 
end

wenn Sie dann das Verhalten definieren müssen für die große und die kleine Ausgabe referenzieren Sie das gemeinsame Verhalten mit die Methode it_should_behave_like ().

describe "SmallEdition" do  
  it_should_behave_like "all editions"
  it "should also behave like a small edition" do   
  end 
end
23
Andrius

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 im Vorhergehenden

Wann ist RSpec let () zu verwenden?

195
metakungfu

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
107
Karmen Blake

Für Module, die isoliert getestet werden können oder die Klasse verspottet, gefällt mir Folgendes:

modul:

module MyModule
  def hallo
    "hallo"
  end
end

spec:

describe MyModule do
  include MyModule

  it { hallo.should == "hallo" }
end

Es scheint falsch zu sein, verschachtelte Beispielgruppen zu hijacken, aber ich mag die Pracht. Irgendwelche Gedanken?

29

Könnten Sie aus Ihrem Kopf heraus eine Dummy-Klasse in Ihrem Testskript erstellen und das Modul in dieses integrieren? Testen Sie dann, ob die Dummy-Klasse das von Ihnen erwartete Verhalten aufweist.

BEARBEITEN: Wenn das Modul, wie in den Kommentaren ausgeführt, erwartet, dass einige Verhalten in der Klasse vorhanden sind, in die es gemischt ist, dann würde ich versuchen, Dummys dieser Verhaltensweisen zu implementieren. Gerade genug, um das Modul glücklich zu machen, um seine Aufgaben zu erfüllen.

Das heißt, ich wäre etwas nervös, wenn ein Modul eine ganze Menge von seiner Host-Klasse erwartet (sagen wir "Host"?) die neue Funktionalität in den Vererbungsbaum, dann denke ich, ich würde versuchen, die Erwartungen eines Moduls zu minimieren. Meine Sorge ist, dass mein Design einige Bereiche mit unangenehmer Inflexibilität entwickeln würde.

21
Mike Woodhouse

Die akzeptierte Antwort ist die richtige Antwort, denke ich, aber ich wollte ein Beispiel hinzufügen, wie man die Methoden rpsecs shared_examples_for und it_behaves_like verwendet. Ich erwähne ein paar Tricks im Code-Snippet, aber für weitere Informationen siehe relishapp-rspec-guide

Damit können Sie Ihr Modul in allen Klassen testen, in denen es enthalten ist. 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 können wir eine Spezifikation für unser Modul erstellen: 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
9
p1100i

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
6
Matt Connolly

Ich würde vorschlagen, dass für größere und viel genutzte Module die von @Andrius hier vorgeschlagenen "Shared Example Groups" gewählt werden sollten. Für einfache Dinge, für die Sie sich nicht die Mühe machen müssen, mehrere Dateien usw. zu haben, können Sie die Sichtbarkeit Ihres Dummy-Dings (so wie Sie es mit rspec 2.14.6 getestet haben) einfach kontrollieren und kopieren Spezifikationsdatei und führe 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
6
Timo

meine jüngste Arbeit, so wenig wie möglich fest verdrahtet

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.

5
Leif

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

3
Uri

sie müssen Ihr Modul einfach in Ihre Spezifikationsdatei aufnehmen mudule Test module MyModule def test 'test' end end end in Ihrer Spezifikationsdatei 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

0
mdlx