Menguji modul dalam rspec

175

Apa praktik terbaik pada modul pengujian di rspec? Saya memiliki beberapa modul yang disertakan dalam beberapa model dan untuk saat ini saya hanya memiliki tes duplikat untuk setiap model (dengan beberapa perbedaan). Apakah ada cara untuk KERING itu?

Andrius
sumber

Jawaban:

219

Cara rad =>

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

Atau Anda dapat menambah kelas tes dengan modul Anda:

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

Menggunakan 'let' lebih baik daripada menggunakan variabel instan untuk mendefinisikan kelas dummy di sebelumnya (: masing-masing)

Kapan menggunakan RSpec let ()?

metakungfu
sumber
1
Bagus. Ini membantu saya menghindari segala macam masalah dengan tes rentang kelas ivars. Memberi nama kelas dengan menugaskan konstanta.
Kapten
3
@lulalala Tidak, ini kelas super: ruby-doc.org/core-2.0.0/Class.html#method-c-new Untuk menguji modul lakukan sesuatu seperti ini:let(:dummy_class) { Class.new { include ModuleToBeTested } }
Timo
26
Cara rad. Saya biasanya melakukan let(:class_instance) { (Class.new { include Super::Duper::Module }).new }:, dengan cara itu saya mendapatkan variabel instan yang paling sering digunakan untuk pengujian dengan cara apa pun.
Otomatis
3
menggunakan includetidak bekerja untuk saya tetapi extendtidaklet(:dummy_class) { Class.new { extend ModuleToBeTested } }
Mike W
8
Bahkan radder:subject(:instance) { Class.new.include(described_class).new }
Richard-Degenne
108

Apa kata Mike. Berikut ini contoh sepele:

kode modul ...

module Say
  def hello
    "hello"
  end
end

fragmen spesifikasi ...

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
Karmen Blake
sumber
3
Apa alasan Anda tidak include Sayberada di dalam deklarasi DummyClass alih-alih menelepon extend?
Berikan Birchmeier
2
hibah-birchmeier, dia extendmasuk ke instance kelas, yaitu setelah newdipanggil. Jika Anda melakukan ini sebelumnya newdipanggil maka Anda benar Anda akan menggunakaninclude
Hedgehog
8
Saya mengedit kode agar lebih ringkas. @dummy_class = Class.new {extended Say} adalah semua yang Anda butuhkan untuk menguji modul. Saya menduga orang akan lebih suka itu karena kami pengembang sering tidak suka mengetik lebih dari yang diperlukan.
Tim Harper
@TimHarper Mencoba tetapi metode instan menjadi metode kelas. Pikiran?
lulalala
6
Mengapa Anda mendefinisikan DummyClasskonstanta? Kenapa tidak adil @dummy_class = Class.new? Sekarang Anda mencemari lingkungan pengujian Anda dengan definisi kelas yang tidak perlu. DummyClass ini didefinisikan untuk setiap spesifikasi Anda dan dalam spesifikasi berikutnya di mana Anda memutuskan untuk menggunakan pendekatan yang sama dan membuka kembali definisi DummyClass mungkin sudah mengandung sesuatu (meskipun dalam contoh sepele ini definisi itu benar-benar kosong, dalam kehidupan nyata kasus penggunaan kemungkinan ada sesuatu yang ditambahkan di beberapa titik dan kemudian pendekatan ini menjadi berbahaya.)
Timo
29

Untuk modul yang dapat diuji secara terpisah atau dengan mengejek kelas, saya menyukai sesuatu seperti:

modul:

module MyModule
  def hallo
    "hallo"
  end
end

spec:

describe MyModule do
  include MyModule

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

Mungkin keliru untuk membajak kelompok contoh bersarang, tapi saya suka kesempitan itu. Adakah pikiran?

Frank C. Schuetz
sumber
1
Saya suka ini, sangat mudah.
iain
2
Mungkin mengacaukan rspec. Saya pikir menggunakan letmetode yang dijelaskan oleh @metakungfu lebih baik.
Otomatis
@ Cort3z Anda harus memastikan bahwa nama metode tidak bertabrakan. Saya menggunakan pendekatan ini hanya ketika semuanya benar-benar sederhana.
Frank C. Schuetz
Ini mengacaukan ruang tes saya karena tabrakan nama.
roxxypoxxy
24

Saya menemukan solusi yang lebih baik di beranda rspec. Tampaknya itu mendukung contoh grup yang dibagikan. Dari https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !

Grup Contoh yang Dibagikan

Anda dapat membuat grup contoh bersama dan memasukkan grup-grup itu ke dalam grup lain.

Misalkan Anda memiliki beberapa perilaku yang berlaku untuk semua edisi produk Anda, baik besar maupun kecil.

Pertama, faktor perilaku "berbagi":

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

kemudian ketika Anda perlu mendefinisikan perilaku untuk edisi Besar dan Kecil, referensi perilaku bersama menggunakan metode it_should_behave_like ().

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

Dari atas kepala saya, bisakah Anda membuat kelas dummy dalam skrip pengujian Anda dan memasukkan modul ke dalamnya? Kemudian uji bahwa kelas dummy memiliki perilaku seperti yang Anda harapkan.

EDIT: Jika, seperti yang ditunjukkan dalam komentar, modul mengharapkan beberapa perilaku untuk hadir di kelas yang dicampur, maka saya akan mencoba untuk menerapkan boneka perilaku tersebut. Cukup membuat modul senang melakukan tugasnya.

Yang mengatakan, saya akan sedikit gugup tentang desain saya ketika sebuah modul mengharapkan banyak dari kelas hostnya (apakah kita mengatakan "host"?) - Jika saya belum mewarisi dari kelas dasar atau tidak dapat menyuntikkan fungsi baru ke dalam pohon warisan maka saya pikir saya akan mencoba untuk meminimalkan harapan yang mungkin dimiliki modul. Kekhawatiran saya adalah bahwa desain saya akan mulai mengembangkan beberapa bidang yang tidak fleksibel.

Mike Woodhouse
sumber
Bagaimana jika modul saya tergantung pada kelas yang memiliki atribut dan perilaku tertentu?
Andrius
10

Jawaban yang diterima adalah jawaban yang tepat menurut saya, namun saya ingin menambahkan contoh bagaimana menggunakan rpsec shared_examples_fordan it_behaves_likemetode. Saya menyebutkan beberapa trik dalam cuplikan kode tetapi untuk info lebih lanjut lihat panduan relishapp-rspec ini .

Dengan ini, Anda dapat menguji modul Anda di salah satu kelas yang menyertakannya. Jadi Anda benar-benar menguji apa yang Anda gunakan dalam aplikasi Anda.

Mari kita lihat sebuah contoh:

# 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

Sekarang mari kita buat spec untuk modul kami: 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
p1100i
sumber
6

Bagaimana dengan:

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

Saya akan menyarankan bahwa untuk modul yang lebih besar dan banyak digunakan orang harus memilih "Grup Contoh Bersama" seperti yang disarankan oleh @ Andrius di sini . Untuk hal-hal sederhana yang Anda tidak ingin mengalami masalah memiliki banyak file dll. Di sini adalah cara untuk memastikan kontrol maksimum atas visibilitas barang dummy Anda (diuji dengan rspec 2.14.6, cukup salin dan tempel kode ke dalam file spesifikasi dan jalankan):

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
Timo
sumber
Untuk beberapa alasan hanya subject { dummy_class.new }berfungsi. Kasing subject { dummy_class }tidak bekerja untuk saya.
valk
6

pekerjaan saya baru-baru ini, menggunakan kabel sekecil mungkin

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

saya harap

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

bekerja, tetapi tidak (seperti pada Ruby MRI 2.2.3 dan 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>

Jelas diuraikan_class tidak terlihat dalam lingkup itu.

Leif
sumber
6

Untuk menguji modul Anda, gunakan:

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

  # examples
end

Untuk KERING beberapa hal yang Anda gunakan di beberapa spesifikasi, Anda dapat menggunakan konteks bersama:

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

Sumber:

Allison
sumber
0

Anda hanya perlu memasukkan modul Anda ke file mudule Test module MyModule def test 'test' end end end spec Anda di file spec Anda 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

mdlx
sumber
-1

Salah satu solusi yang mungkin untuk menguji metode modul yang independen pada kelas yang akan memasukkannya

module moduleToTest
  def method_to_test
    'value'
  end
end

Dan spec untuk itu

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

Dan jika Anda ingin KERING mengujinya, maka shared_examples adalah pendekatan yang baik

Nermin
sumber
Saya bukan orang yang menurunkan Anda, tetapi saya sarankan mengganti dua LET Anda dengan subject(:module_to_test_instance) { Class.new.include(described_class) }. Kalau tidak, saya tidak benar-benar melihat ada yang salah dengan jawaban Anda.
Allison
-1

Ini adalah pola berulang karena Anda perlu menguji lebih dari satu modul. Karena itu, ini lebih dari sekadar diinginkan untuk membuat pembantu untuk ini.

Saya menemukan posting ini yang menjelaskan bagaimana melakukannya tetapi saya berupaya di sini karena situs tersebut mungkin akan dihapus pada beberapa titik.

Ini untuk menghindari instance objek, jangan mengimplementasikan metode instance:: kesalahan apa pun yang Anda dapatkan saat mencoba allowmetodedummy kelas.

Kode:

Di 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

Di 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

Dalam spesifikasi Anda:

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
juliangonzalez
sumber