RSpecでのDRY原則の適用





DRY(Do n't Repeat Yourself)は、特にルビープログラマーの間では、現代の開発の基礎の1つです。 しかし、通常のコードを記述するときに、フラグメントの繰り返しをメソッドまたは個別のモジュールに簡単にグループ化できる場合、テストを記述するときに、コードの繰り返しがさらに多くなることがあるため、これは必ずしも簡単ではありません。 この記事では、RSpec BDDフレームワークを使用する場合のこのような問題の解決策の簡単な概要を示します。



1.共有された例



Rspecの再利用可能なコードを作成するための最も有名で一般的に使用される方法。 クラスの継承とモジュールの包含をテストするのに最適です。



shared_examples "coolable" do let(:target){described_class.new} it "should make cool" do target.make_cool target.should be_cool end end describe User do it_should_behave_like "coolable" end
      
      





さらに、Shared Example Groupにはいくつかの追加機能があり、それらを使用することでより柔軟になります。パラメータを渡す、ブロックを渡す、メソッドを定義するために親グループでletを使用する。



 shared_examples "coolable" do |target_name| it "should make #{ target_name } cool" do target.make_cool target.should be_cool end end describe User do it_should_behave_like "coolable", "target user" do let(:target){User.new} end end
      
      





特定の方法がどこでどのように利用可能になるかの詳細については、David Chelimsky [2]をお読みください。



2.共有コンテキスト



この機能は、(RSpec 2.6で表示された)比較的新しいものであり、範囲が狭いため、いくぶん不明です。 共有コンテキストを使用するのに最も適した状況は、同じ初期値または最終アクションを必要とするいくつかの仕様が存在することです。通常、前と後のブロックで指定されます。 ドキュメントはこれを示唆しています:



 shared_context "shared stuff", :a => :b do before { @some_var = :some_value } def shared_method "it works" end let(:shared_let) { {'arbitrary' => 'object'} } subject do 'this is the subject' end end
      
      





shared_contextで非常に便利なのは、describeブロックで指定されたメタ情報に従ってそれらを含める機能です。



 shared_context "shared with somevar", :need_values => 'some_var' do before { @some_var = :some_value } end describe "need som_var", :need_values => 'some_var' do it “should have som_var” do @some_var.should_not be_nil end end
      
      







3.工場設備



別のシンプルだが非常に重要なポイント。



 @user = User.create( :email => 'example@example.com', :login => 'login1', :password => 'password', :status => 1, … )
      
      





このような構成を繰り返し記述する代わりに、 factory_girl gemまたはその類似物を使用する必要があります。 利点は明らかです。コードの量が削減され、ステータスをstatus_codeに変更することにした場合、すべての仕様を書き直す必要はありません。



4.自分のマッチャー



独自のゲーマーを定義する機能は、RSpecで最もクールな機能の1つです。これにより、仕様の読みやすさと優雅さを非現実的に高めることができます。 すぐに例。

宛先:

 it “should make user cool” do make_cool(user) user.coolness.should > 100 user.rating.should > 10 user.cool_things.count.should == 1 end
      
      





後:

 RSpec::Matchers.define :be_cool do match do |actual| actual.coolness.should > 100 && actual.rating.should > 10 && actual.cool_things.count.should == 1 end end it “should make user cool” do make_cool(user) user.should be_cool end
      
      





同意して、はるかに良くなった。

RSpecを使用すると、独自のマッチャーにエラーメッセージを設定し、説明を表示し、チェイニングを実行できます。これにより、マッチャーが非常に柔軟になり、組み込みのマッチャーと違いがなくなります。 それらを最大限に活用するために、次の例を提案します[1]。



 RSpec::Matchers.define :have_errors_on do |attribute| chain :with_message do |message| @message = message end match do |model| model.valid? @has_errors = model.errors.key?(attribute) if @message @has_errors && model.errors[attribute].include?(@message) else @has_errors end end failure_message_for_should do |model| if @message "Validation errors #{model.errors[attribute].inspect} should include #{@message.inspect}" else "#{model.class} should have errors on attribute #{attribute.inspect}" end end failure_message_for_should_not do |model| "#{model.class} should not have an error on attribute #{attribute.inspect}" end end
      
      







5.単一行



RSpecは、単純な仕様を記述するために単一行構文を使用する機能を提供します。



実際のオープンソースプロジェクトの例( kaminari ):

 context 'page 1' do subject { User.page 1 } it { should be_a Mongoid::Criteria } its(:current_page) { should == 1 } its(:limit_value) { should == 25 } its(:total_pages) { should == 2 } it { should skip(0) } end end
      
      





明らかにはるかに優れています:

 context 'page 1' do before :each do @page = User.page 1 end it “should be a Mongoid criteria” do @page.should be_a Mongoid::Criteria end it “should have current page set to 1do @page.current_page.should == 1 end …. #etc
      
      







6.動的に作成された仕様



ここでの重要な点は、it構築(およびコンテキストと記述)が、コードブロックを最後の引数としてとる単なるメソッドであるということです。 したがって、それらはサイクルや条件で呼び出すことができ、そのような構造を構成することさえできます:



 it(it("should process +"){(2+3).should == 5}) do (3-2).should == 1 end
      
      





どちらの仕様も成功していますが、同じループやイテレーターとは異なり、これをどこに適用できるか考えることさえ怖いです。 同じ雷からの例:



 [User, Admin, GemDefinedModel].each do |model_class| context "for #{model_class}" do describe '#page' do context 'page 1' do subject { model_class.page 1 } it_should_behave_like 'the first page' endend end end
      
      





または条件付きの例:



 if Mongoid::VERSION =~ /^3/ its(:selector) { should == {'salary' => 1} } else its(:selector) { should == {:salary => 1} } end
      
      







7.マクロ



2010年、David Chelimskyは、共有サンプルの新しい機能の導入後、マクロはもう必要ないと述べました。 ただし、これが仕様のコードを改善する最も適切な方法であるとまだ考えている場合は、次のように作成できます。



 module SumMacro def it_should_process_sum(s1, s2, result) it "should process sum of #{s1} and #{s2}" do (s1+s2).should == result end end end describe "sum" do extend SumMacro it_should_process_sum 2, 3, 5 end
      
      





この点については詳しくはわかりませんが、必要に応じて[4]を読むことができます。



8. LetとSubject



letおよびsubjectコンストラクトは、仕様を実行する前に初期値を初期化するために必要です。 もちろん、すべての仕様で次のように書くことは誰もがすでに認識しています。

 it “should do something” do user = User.new … end
      
      





まったく素晴らしいわけではありませんが、通常は誰もがこのコードを前に押し込みます:



 before :each do @user = user.new end
      
      





ただし、件名を使用する必要があります。 また、サブジェクトが排他的に「名前なし」であった場合、定義する変数の名前を指定することで明示的に使用することもできます。



 describe "number" do subject(:number){ 5 } it "should eql 5" do number.should == 5 end end
      
      







Letはsubjectと似ていますが、メソッドの宣言に使用されます。



サイトリンク



1.カスタムRSpec-2マッチャー

solnic.eu/2011/01/14/custom-rspec-2-matchers.html

2. David Chelimsky-RSpec-2で共有されたサンプルグループでミックスインを指定する

blog.davidchelimsky.net/2010/11/07/specifying-mixins-with-shared-example-groups-in-rspec-2

3. Ben Scheirman-件名とLetブロックでRspecファイルをドライアップ

benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks

4. Ben Mabey-RSpecでマクロを書く

benmabey.com/2008/06/08/writing-macros-in-rspec.html



そして結論として、私は言うことができます、私は言うことができるだけです-より少なく繰り返すようにしてください。



All Articles