2016-05-12 9 views
4

RSpecのany_instance_ofメソッド(例:expect_any_instance_of)を使うのは悪い習慣であるという文章に遭遇したようです。良質なドキュメントでは、「レガシーコードを使って作業する」セクション(http://www.relishapp.com/rspec/rspec-mocks/v/3-4/docs/working-with-legacy-code/any-instance)にこれらのメソッドをリストしています。これは、これを利用して新しいコードを書くべきではないことを意味します。新しいインスタンスのメソッドを呼び出す関数をテストする "正しい"方法は何ですか?

私はこの機能に依存する新しい仕様を日常的に書いているように感じています。優れた例は、新しいインスタンスを作成し、その上でメソッドを呼び出すメソッドです。 (mymodelというのActiveRecordのあるレールで)私は定期以下のような何かを行う方法を記述します。

def my_method 
    my_active_record_model = MyModel.create(my_param: my_val) 
    my_active_record_model.do_something_productive 
end 

を私は一般的にexpect_any_instance_ofを使用して呼び出されdo_something_productiveを探している私の仕様を記述します。例えば:

my_double = double('my_model') 
expect(MyModel).to receive(:create).and_return(my_double) 
expect(my_double).to receive(:do_something_productive) 
subject.my_method 

は、しかし、私はこれより悪く考えるので)それが長く、遅くなります:私はこれを仕様に見ることができます

expect_any_instance_of(MyModel).to receive(:do_something_productive) 
subject.my_method 

唯一の他の方法は、このようなスタブの束となりb)それは最初の方法よりはるかに脆い白い箱です。その後、

def my_method 
    my_active_record_model = MyModel.new(my_param: my_val) 
    my_active_record_model.save 
    my_active_record_model.do_something_productive 
end 

スペック切断の二重のバージョンを、しかしany_instance_ofバージョンが正常に動作します:私は次のようにmy_methodを変更した場合、第二の点を説明するために。

私の質問は、他の開発者はどのようにこれをやっているのですか? any_instance_ofを使用して私のアプローチは悩んでいますか?もしそうなら、なぜですか?

答えて

1

あなたが与えた2つのコードよりも優れたコードをテストするための優れたソリューションはありません。スタブ/モックソリューションではcreateコールはcreateコールではexpectではなくallowを使用します。createコールは仕様のポイントではありませんが、それは側面の問題です。私はスタブと嘲笑が苦痛であることに同意しますが、それは通常私がしていることです。

しかし、そのコードにはわずかなFeature Envyしかありません。 MyModelにメソッドを抽出すると、香りをクリアし、テストの問題を排除します。

class MyModel < ActiveRecord::Base 
    def self.create_productively(attrs) 
    create(attrs).do_something_productive 
    end 
end 

def my_method 
    MyModel.create_productively(attrs) 
end 

# in the spec 
expect(MyModel).to receive(:create_productively) 
subject.my_method 

create_productivelyがモデルのメソッドなので、それは、実際のインスタンスでテストする必要があることができ、スタブやモックする必要はありません。

RSpecの一般的に使用されていない機能を使用する必要があることは、私のコードが少しのリファクタリングを使用できることを意味することがよくあります。

+0

'create_productively'メソッドでまったく同じ問題が発生していませんか? –

+0

モデル上でメソッドを作成して呼び出すだけでなく、より多くの作業を行った方がより明らかに役立ちます。しかし、いずれにせよ、私の更新を参照してください。 –

+0

つまり、 'create_productively'をどうやってテストするのでしょうか?私はこれがどのように役立つのか分かりません。たぶん私はそれを見逃しているかもしれない。 –

1

これは暴言の一種であるが、ここで私の考えです:

レリッシュのドキュメントでも、私が書くべきではありませんを意味し、「レガシーコードの操作」(http://www.relishapp.com/rspec/rspec-mocks/v/3-4/docs/working-with-legacy-code/any-instance)の下でこれらのメソッドをリストこれを利用した新しいコード。

私はこれに同意しません。模擬/スタブは、効果的に使用される場合は貴重なツールであり、アサーションスタイルのテストと並行して使用する必要があります。この理由は、擬似/スタブを使用することで、スタック内のすべての小さなdbトランザクション、API呼び出し、またはヘルパーメソッドを呼び出す必要なく、結合を最小限に抑えて高水準の機能をテストできる「外部イン」テストアプローチが可能になるからです。

質問は本当に状態や動作をテストしたいですか?明らかに、あなたのアプリには両方が含まれているので、テストの単一のパラダイムに縛られることは意味がありません。アサーション/期待による伝統的なテストは、状態をテストするのに有効であり、の対応方法はほとんど変更されていません。状態が変更されました。一方で、モックは、オブジェクト間のインタフェースや相互作用について考える必要があります。スタブと戻り値を返すことができるので、状態自体の変更に負担をかけることはありません。 *_any_instance_ofを使用する場合は注意してくださいそれは可能であれば。それは非常に鈍い道具であり、プロジェクトがより大きいときには責任が負うだけの小さなプロジェクトの場合に悪用されやすい。私は通常*_any_instance_ofを自分のコードやテストのいずれかのにおいとして改善することができますが、使用する必要がある時があります。あなたが提案する二つのアプローチの間、私はこの1つを言われていること

、好む:

my_double = double('my_model') 
expect(MyModel).to receive(:create).and_return(my_double) 
expect(my_double).to receive(:do_something_productive) 
subject.my_method 

それはよく分離され、明示的だし、データベース呼び出しとオーバーヘッドが発生しません。 my_methodの実装が変更された場合は、書き換えが必要になる可能性がありますが、それでも問題ありません。よく分離されているので、my_method以外のコードが変更された場合は、書き直す必要はありません。これをデータベースの列を削除することでほぼすべてのテストスイートを破ることができるアサーションとは対照的です。

0
def self.my_method(attrs) 
    create(attrs).tap {|m| m.do_something_productive} 
end 

# Spec 
let(:attrs) { # valid hash } 

describe "when calling my_method with valid attributes" do 
    it "does something productive" do 
    expect(MyModel.my_method(attrs)).to have_done_something_productive 
    end 
end 

当然、#do_something_productive自体のテストがあります。

トレードオフは常に同じです:モックとスタブは高速ですが、脆いです。実際のオブジェクトは遅くなりますが、脆弱ではありません。

外部依存関係(API呼び出しなど)や、定義済みで実装されていないインターフェースを使用する場合は、モック/スタブを予約する傾向があります。

関連する問題