Các thực hành tốt nhất về các mô-đun thử nghiệm trong rspec là gì? Tôi có một số mô-đun được bao gồm trong một số mô hình và bây giờ tôi chỉ đơn giản là có các thử nghiệm trùng lặp cho từng mô hình (với một vài khác biệt). Có cách nào để DRY nó lên?
Các thực hành tốt nhất về các mô-đun thử nghiệm trong rspec là gì? Tôi có một số mô-đun được bao gồm trong một số mô hình và bây giờ tôi chỉ đơn giản là có các thử nghiệm trùng lặp cho từng mô hình (với một vài khác biệt). Có cách nào để DRY nó lên?
Câu trả lời:
Cách rad =>
let(:dummy_class) { Class.new { include ModuleToBeTested } }
Ngoài ra, bạn có thể mở rộng lớp thử nghiệm với mô-đun của mình:
let(:dummy_class) { Class.new { extend ModuleToBeTested } }
Sử dụng 'let' tốt hơn so với sử dụng một biến đối tượng để xác định lớp giả trong trước (: mỗi)
let(:dummy_class) { Class.new { include ModuleToBeTested } }
let(:class_instance) { (Class.new { include Super::Duper::Module }).new }
, bằng cách đó, tôi nhận được biến đối tượng thường được sử dụng để kiểm tra bất kỳ cách nào.
include
không hiệu quả với tôi nhưng extend
khônglet(:dummy_class) { Class.new { extend ModuleToBeTested } }
subject(:instance) { Class.new.include(described_class).new }
Mike nói gì. Đây là một ví dụ tầm thường:
mã mô-đun ...
module Say
def hello
"hello"
end
end
thông số kỹ thuật ...
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
bên trong tuyên bố DummyClass thay vì gọi extend
?
extend
tham gia vào trường hợp của lớp, tức là sau khi new
được gọi. Nếu bạn đang làm điều này trước khi new
được gọi thì bạn đã đúng, bạn sẽ sử dụnginclude
DummyClass
hằng số? Tại sao không chỉ @dummy_class = Class.new
? Bây giờ bạn gây ô nhiễm môi trường thử nghiệm của bạn với một định nghĩa lớp không cần thiết. DummyClass này được định nghĩa cho mọi thông số kỹ thuật của bạn và trong thông số kỹ thuật tiếp theo nơi bạn quyết định sử dụng cùng một cách tiếp cận và mở lại định nghĩa DummyClass, nó có thể chứa một cái gì đó (mặc dù trong ví dụ tầm thường này, định nghĩa này hoàn toàn trống rỗng, trong cuộc sống thực trường hợp sử dụng có khả năng một cái gì đó được thêm vào một lúc nào đó và sau đó phương pháp này trở nên nguy hiểm.)
Đối với các mô-đun có thể được kiểm tra độc lập hoặc bằng cách chế nhạo lớp, tôi thích một cái gì đó dọc theo dòng:
mô-đun:
module MyModule
def hallo
"hallo"
end
end
thông số kỹ thuật:
describe MyModule do
include MyModule
it { hallo.should == "hallo" }
end
Có vẻ sai khi chiếm quyền điều khiển các nhóm ví dụ lồng nhau, nhưng tôi thích sự căng thẳng. Có suy nghĩ gì không?
let
phương pháp được mô tả bởi @metakungfu là tốt hơn.
Tôi tìm thấy một giải pháp tốt hơn trong trang chủ rspec. Rõ ràng nó hỗ trợ các nhóm ví dụ được chia sẻ. Từ https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/spl-examples !
Nhóm ví dụ được chia sẻ
Bạn có thể tạo các nhóm ví dụ được chia sẻ và đưa các nhóm đó vào các nhóm khác.
Giả sử bạn có một số hành vi áp dụng cho tất cả các phiên bản sản phẩm của bạn, cả lớn và nhỏ.
Đầu tiên, yếu tố ra hành vi chia sẻ trên mạng:
shared_examples_for "all editions" do
it "should behave like all editions" do
end
end
sau đó khi bạn cần xác định hành vi cho các phiên bản Lớn và Nhỏ, hãy tham chiếu hành vi được chia sẻ bằng phương thức it_should_behave_like ().
describe "SmallEdition" do
it_should_behave_like "all editions"
it "should also behave like a small edition" do
end
end
Ngoài đỉnh đầu của tôi, bạn có thể tạo một lớp giả trong tập lệnh thử nghiệm của bạn và đưa mô-đun vào đó không? Sau đó kiểm tra xem lớp giả có hành vi theo cách bạn mong đợi không.
EDIT: Nếu, như đã chỉ ra trong các bình luận, mô-đun mong đợi một số hành vi sẽ có mặt trong lớp mà nó bị trộn lẫn, thì tôi sẽ cố gắng thực hiện các hành vi giả của những hành vi đó. Chỉ cần đủ để làm cho các mô-đun hạnh phúc để thực hiện nhiệm vụ của mình.
Điều đó nói rằng, tôi sẽ hơi lo lắng về thiết kế của mình khi một mô-đun mong đợi rất nhiều từ lớp máy chủ của nó (chúng ta có nói là "máy chủ" không?) - Nếu tôi không được thừa kế từ một lớp cơ sở hoặc không thể tiêm chức năng mới vào cây thừa kế sau đó tôi nghĩ rằng tôi đang cố gắng giảm thiểu bất kỳ kỳ vọng nào mà mô-đun có thể có. Mối quan tâm của tôi là thiết kế của tôi sẽ bắt đầu phát triển một số lĩnh vực không linh hoạt khó chịu.
Câu trả lời được chấp nhận là câu trả lời đúng tôi nghĩ, tuy nhiên tôi muốn thêm một ví dụ về cách sử dụng rpsecs shared_examples_for
và it_behaves_like
phương thức. Tôi đề cập đến một vài thủ thuật trong đoạn mã nhưng để biết thêm thông tin, hãy xem hướng dẫn relishapp-rspec này .
Với điều này, bạn có thể kiểm tra mô-đun của mình trong bất kỳ lớp nào bao gồm nó. Vì vậy, bạn thực sự đang thử nghiệm những gì bạn sử dụng trong ứng dụng của bạn.
Hãy xem một ví dụ:
# 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
Bây giờ hãy tạo spec cho mô-đun của chúng tôi: 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
Thế còn:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end
Tôi sẽ đề nghị rằng đối với các mô-đun lớn hơn và được sử dụng nhiều, bạn nên chọn "Nhóm ví dụ được chia sẻ" theo đề xuất của @Andrius tại đây . Đối với những thứ đơn giản mà bạn không muốn gặp phải sự cố khi có nhiều tệp, v.v ... đây là cách đảm bảo kiểm soát tối đa khả năng hiển thị của công cụ giả của bạn (đã thử nghiệm với rspec 2.14.6, chỉ cần sao chép và dán mã vào spec file và chạy 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
subject { dummy_class.new }
là làm việc. Trường hợp subject { dummy_class }
không làm việc cho tôi.
công việc gần đây của tôi, sử dụng càng ít dây càng tốt
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
Tôi ước
subject {Class.new{include described_class}.new}
đã hoạt động nhưng không được (như tại Ruby MRI 2.2.3 và 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>
Rõ ràng mô tả_ class không thể nhìn thấy trong phạm vi đó.
Để kiểm tra mô-đun của bạn, sử dụng:
describe MyCoolModule do
subject(:my_instance) { Class.new.extend(described_class) }
# examples
end
Để DRY up một số thứ bạn sử dụng trên nhiều thông số kỹ thuật, bạn có thể sử dụng bối cảnh được chia sẻ:
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
Tài nguyên:
Bạn cũng có thể sử dụng loại người trợ giúp
# 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
Đây là tài liệu: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec
bạn chỉ cần bao gồm mô-đun của bạn
mudule Test
module MyModule
def test
'test'
end
end
end
vào tệp spec của bạn trong tệp spec của bạn
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
Một giải pháp khả thi để thử nghiệm phương thức mô-đun độc lập trên lớp sẽ bao gồm chúng
module moduleToTest
def method_to_test
'value'
end
end
Và thông số kỹ thuật cho nó
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
Và nếu bạn muốn DRY kiểm tra chúng, thì shared_examples là cách tiếp cận tốt
subject(:module_to_test_instance) { Class.new.include(described_class) }
. Nếu không, tôi không thực sự thấy bất cứ điều gì sai với câu trả lời của bạn.
Đây là một mẫu lặp lại vì bạn sẽ cần kiểm tra nhiều hơn một mô-đun. Vì lý do đó, điều này là hơn cả mong muốn để tạo ra một người trợ giúp cho việc này.
Tôi tìm thấy bài đăng này giải thích cách thực hiện nhưng tôi đang đối phó ở đây vì trang web có thể bị gỡ xuống tại một số điểm.
Điều này là để tránh các thể hiện đối tượng không thực hiện phương thức cá thể :: bất kỳ lỗi nào bạn gặp phải khi thử allow
các phương thức trên dummy
lớp.
Trong 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
Trong 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
Trong thông số kỹ thuật của bạn:
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