อะไรคือแนวปฏิบัติที่ดีที่สุดสำหรับโมดูลการทดสอบใน rspec ฉันมีโมดูลบางตัวที่รวมอยู่ในรุ่นไม่กี่รุ่นและตอนนี้ฉันมีการทดสอบซ้ำกันสำหรับแต่ละรุ่น (มีความแตกต่างเล็กน้อย) มีวิธีที่จะทำให้มันแห้งหรือไม่?
อะไรคือแนวปฏิบัติที่ดีที่สุดสำหรับโมดูลการทดสอบใน rspec ฉันมีโมดูลบางตัวที่รวมอยู่ในรุ่นไม่กี่รุ่นและตอนนี้ฉันมีการทดสอบซ้ำกันสำหรับแต่ละรุ่น (มีความแตกต่างเล็กน้อย) มีวิธีที่จะทำให้มันแห้งหรือไม่?
คำตอบ:
วิธี rad =>
let(:dummy_class) { Class.new { include ModuleToBeTested } }
หรือคุณสามารถขยายคลาสการทดสอบด้วยโมดูลของคุณ:
let(:dummy_class) { Class.new { extend ModuleToBeTested } }
การใช้ 'ให้' ดีกว่าการใช้ตัวแปรอินสแตนซ์เพื่อกำหนดระดับหุ่นจำลองในก่อนหน้า (: แต่ละรายการ)
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
ชิ้นส่วน spec ...
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
คุณสามารถสร้างชั้นเรียนจำลองในสคริปต์ทดสอบของคุณและรวมโมดูลไว้ในนั้นได้หรือไม่ จากนั้นทดสอบว่าระดับหุ่นจำลองมีพฤติกรรมในแบบที่คุณคาดหวัง
แก้ไข: ถ้าตามที่ระบุไว้ในความคิดเห็นโมดูลคาดว่าพฤติกรรมบางอย่างที่จะนำเสนอในชั้นเรียนซึ่งเป็นที่ผสมแล้วฉันจะพยายามที่จะใช้หุ่นของพฤติกรรมเหล่านั้น เพียงพอที่จะทำให้โมดูลมีความสุขในการปฏิบัติหน้าที่
ที่กล่าวว่าฉันจะกังวลเล็กน้อยเกี่ยวกับการออกแบบของฉันเมื่อโมดูลคาดหวังมากจากโฮสต์ (เราจะพูดว่า "host"?) class - ถ้าฉันไม่ได้รับมรดกจากชั้นฐานหรือไม่สามารถฉีด ฟังก์ชั่นใหม่ในแผนผังการสืบทอดจากนั้นฉันคิดว่าฉันจะพยายามลดความคาดหวังใด ๆ ที่โมดูลอาจมี ความกังวลของฉันคือการที่การออกแบบของฉันจะเริ่มพัฒนาบางส่วนของความยืดหยุ่นที่ไม่พึงประสงค์
คำตอบที่ยอมรับคือคำตอบที่ถูกต้องฉันคิดว่าฉันต้องการเพิ่มตัวอย่างวิธีใช้ 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
ผมขอแนะนำว่าสำหรับขนาดใหญ่และโมดูลใช้มากควรเลือกหนึ่งสำหรับ "ที่ใช้ร่วมกันตัวอย่างกลุ่ม" ตามที่แนะนำโดย @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>
ชัดเจน __class จะไม่ปรากฏในขอบเขตนั้น
เพื่อทดสอบโมดูลของคุณใช้:
describe MyCoolModule do
subject(:my_instance) { Class.new.extend(described_class) }
# examples
end
ในการ DRY บางสิ่งที่คุณใช้กับสเปคหลาย ๆ ตัวคุณสามารถใช้บริบทที่แชร์:
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
และหากคุณต้องการทดสอบพวกเขาให้แห้งแล้ว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