Rspec'de test modülleri


175

Rspec modüllerini test etme konusunda en iyi uygulamalar nelerdir? Birkaç modele dahil bazı modüller var ve şimdilik sadece her model için (birkaç fark ile) yinelenen testler var. KURUTMAK için bir yol var mı?

Yanıtlar:


219

Rad yolu =>

let(:dummy_class) { Class.new { include ModuleToBeTested } }

Alternatif olarak, test sınıfını modülünüzle genişletebilirsiniz:

let(:dummy_class) { Class.new { extend ModuleToBeTested } }

'Let' kullanmak, önceki (: each) içindeki kukla sınıfı tanımlamak için bir örnek değişkeni kullanmaktan daha iyidir

RSpec let () ne zaman kullanılır?


1
Güzel. Bu, sınıf ivars yayılma testleriyle ilgili her türlü sorunu önlememe yardımcı oldu. Sabitlere atayarak sınıf isimlerini ver.
captainpete

3
@lulalala Hayır, süper bir sınıf: ruby-doc.org/core-2.0.0/Class.html#method-c-new Modülleri test etmek için şu şekilde bir şey yapın:let(:dummy_class) { Class.new { include ModuleToBeTested } }
Timo

26
Yol rad. Genellikle yaparım:, let(:class_instance) { (Class.new { include Super::Duper::Module }).new }bu şekilde herhangi bir şekilde test için en sık kullanılan örnek değişkeni alırım.
Automatico

3
kullanarak includebenim için çalışmıyor ama extenddoeslet(:dummy_class) { Class.new { extend ModuleToBeTested } }
Mike W

8
Hatta radder:subject(:instance) { Class.new.include(described_class).new }
Richard-Degenne

108

Mike ne dedi. İşte önemsiz bir örnek:

modül kodu ...

module Say
  def hello
    "hello"
  end
end

spec parçası ...

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

3
include SayArama yapmak yerine DummyClass beyanının içinde olmamanız için herhangi bir neden var extendmı?
Grant Birchmeier

2
hibe-birchmeier, extendsınıf örneğine giriyor, yani sonra newçağrıldı. Bunu daha önce newyapsaydınız o zaman haklısıninclude
Hedgehog

8
Kodu daha özlü olacak şekilde düzenledim. @dummy_class = Class.new {genişlet Say} bir modülü test etmek için ihtiyacınız olan tek şey. İnsanların tercih ettiğinden şüpheleniyorum, çünkü biz geliştiriciler genellikle gereğinden fazla yazmayı sevmiyorlar.
Tim Harper

@TimHarper Denendi ancak örnek yöntemler sınıf yöntemleri haline geldi. Düşünceler?
lulalala

6
DummyClassSabiti neden tanımlarsınız ? Neden sadece @dummy_class = Class.new? Artık gereksiz bir sınıf tanımıyla test ortamınızı kirletiyorsunuz. Bu DummyClass, özelliklerinizin her biri için tanımlanmıştır ve aynı yaklaşımı kullanmaya karar verdiğiniz ve bir şey içerebileceği DummyClass tanımını yeniden açtığınız bir sonraki spesifikasyonda (bu önemsiz örnekte tanım gerçek hayatta boş olsa da) vakaları kullanmanız bir noktada bir şeylerin eklenmesi ve bu yaklaşımın tehlikeli hale gelmesi muhtemeldir.)
Timo

29

Tek başına ya da sınıf alay ederek test edilebilen modüller için, aşağıdakiler boyunca bir şeyden hoşlanırım:

modülü:

module MyModule
  def hallo
    "hallo"
  end
end

spec:

describe MyModule do
  include MyModule

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

Yuvalanmış örnek gruplarını ele geçirmek yanlış görünebilir, ancak ben küstahlığı seviyorum. Düşüncesi olan var mı?


1
Bunu beğendim, çok basit.
Iain

2
Rspec'i bozabilir. let@Metakungfu tarafından açıklanan yöntemi kullanmak daha iyi olduğunu düşünüyorum .
Automatico

@ Cort3z Kesinlikle yöntem adlarının çarpışmadığından emin olmanız gerekir. Bu yaklaşımı sadece işler gerçekten basit olduğunda kullanıyorum.
Frank C. Schuetz

Bu, isim çarpışması nedeniyle test takımımı berbat etti.
17x18

24

Rspec ana sayfasında daha iyi bir çözüm buldum. Görünüşe göre paylaşılan örnek grupları destekliyor. Gönderen https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !

Paylaşılan Örnek Gruplar

Paylaşılan örnek gruplar oluşturabilir ve bu grupları diğer gruplara dahil edebilirsiniz.

Ürününüzün büyük veya küçük tüm sürümleri için geçerli bazı davranışlarınız olduğunu varsayalım.

İlk olarak, “paylaşılan” davranışı hesaba katın:

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

Büyük ve Küçük sürümler için davranışı tanımlamanız gerektiğinde, paylaşılan davranışı it_should_behave_like () yöntemini kullanarak referans alın.

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


21

Başımın üstünden, test komut dosyanızda bir kukla sınıf oluşturabilir ve modülü buna dahil edebilir misiniz? Sonra kukla sınıfın davranışın beklediğiniz şekilde olduğunu test edin.

DÜZENLEME: Yorumlarda belirtildiği gibi modül, karıştırıldığı sınıfta bazı davranışların olmasını beklerse, o zaman bu davranışların mankenlerini uygulamaya çalışırım. Modülü görevlerini yerine getirmekten mutlu edecek kadar.

Bununla birlikte, bir modül ana bilgisayarından çok şey beklediğinde tasarımım hakkında biraz gergin olurdum ("host" diyoruz?) - Bir temel sınıftan miras almazsam veya enjekte edemezsem miras ağacına yeni işlevsellik o zaman bir modülün sahip olabileceği beklentileri en aza indirmeye çalışacağımı düşünüyorum. Benim endişem, tasarımımın hoş olmayan esnek olmayan bazı alanlar geliştirmeye başlamasıdır.


Modülüm sınıfın belirli niteliklere ve davranışlara sahip olmasına bağlıysa ne olur?
Andrius

10

Kabul edilen cevap bence doğru cevaptır, ancak rpsec shared_examples_forve it_behaves_likeyöntemlerin nasıl kullanılacağına dair bir örnek eklemek istedim . Kod snippet'inde birkaç numaradan bahsediyorum, ancak daha fazla bilgi için bu relishapp-rspec-rehberine bakın .

Bununla modülünüzü içeren sınıflardan herhangi birinde test edebilirsiniz. Yani uygulamanızda kullandıklarınızı gerçekten test ediyorsunuz.

Bir örnek görelim:

# 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

Şimdi modülümüz için spec oluşturalım: 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

6

Ne dersin:

describe MyModule do
  subject { Object.new.extend(MyModule) }
  it "does stuff" do
    expect(subject.does_stuff?).to be_true
  end
end

6

Ben daha büyük ve çok kullanılan modüller için burada @Andrius tarafından önerilen "Paylaşılan Örnek Grupları" tercih gerektiğini öneriyoruz . Birden fazla dosyaya sahip olma gibi sorunlardan geçmek istemediğiniz basit şeyler için, kukla şeylerinizin görünürlüğü üzerinde maksimum kontrolü nasıl sağlayacağınız aşağıda açıklanmıştır (rspec 2.14.6 ile test edilmiştir, kodu kopyalayıp bir spec dosyası ve çalıştırın):

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

Nedense sadece subject { dummy_class.new }çalışıyor. Dava subject { dummy_class }benim için çalışmıyor.
valk

6

son işim, mümkün olduğunca az kablolama kullanarak

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

Keşke

subject {Class.new{include described_class}.new}

çalıştı, ancak çalışmıyor (Ruby MRI 2.2.3 ve RSpec :: Core 3.3.0'da olduğu gibi)

Failure/Error: subject {Class.new{include described_class}.new}
  NameError:
    undefined local variable or method `described_class' for #<Class:0x000000063a6708>

Açıkça açıklandığı gibi, bu kapsamda görünmez.


6

Modülünüzü test etmek için şunu kullanın:

describe MyCoolModule do
  subject(:my_instance) { Class.new.extend(described_class) }

  # examples
end

Birden fazla özellikte kullandığınız bazı şeyleri KURUTMAK için, paylaşılan bir bağlam kullanabilirsiniz:

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

Kaynaklar:



0

basitçe spec dosyasına modülü eklemeniz gereken mudule Test module MyModule def test 'test' end end end spec dosyasında 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


-1

Bunları içeren sınıfta bağımsız olan modül yöntemini test etmek için olası bir çözüm

module moduleToTest
  def method_to_test
    'value'
  end
end

Ve bunun için spec

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

Ve onları KURU test etmek istiyorsanız, paylaşılan_örnekler iyi bir yaklaşımdır


Seni küçümseyen ben değildim, ama iki LET'inizi değiştirmenizi öneririm subject(:module_to_test_instance) { Class.new.include(described_class) }. Aksi takdirde cevabınızla ilgili yanlış bir şey görmüyorum.
Allison

-1

Birden fazla modülü test etmeniz gerekeceğinden bu tekrarlayan bir modeldir. Bu nedenle, bunun için bir yardımcı oluşturmak istenir.

Bulduğum bu yazı yapmak ancak site bir noktada aşağı alınabilir beri buraya Başa çıkıyorum açıklar.

Bu, nesne örneklerinin örnek yöntemini uygulamamasını önlemek içindir :: sınıftaki allowyöntemleri denerken aldığınız hata dummy.

Kod:

İçinde 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

İçinde 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

Teknik özelliklerinizde:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.