これをrspecでどのようにテストしますか?
class SomeClass
def map_url(size)
GoogleMap.new(point: model.location.point, size: size).map_url
end
end
テストが「非常に結合されていて、モックに対してもろい」ように見えるという事実は、コード自体が一度に多くのことを実行していることを示しています。
問題を浮き彫りにするために、この実装を見てくださいmap_url
。これは無意味であり(任意のサイズの入力に対して「foo」を返す)、それでもテストに合格します。
class SomeClass
def map_url(size)
GoogleMap.new.map_url
GoogleMap.new(point: model.location.point, size: size)
return "foo"
end
end
次のことに注意してください。
map_url
新しく開始されたマップで呼び出されていますが、正しい引数で開始されたマップではありません。map_url
返されません。問題は、コードを構造化した方法によって、実際よりも単純に見えることだと思います。その結果、テストが単純すぎて、メソッドの動作を完全にカバーするには不十分です。
デビッドチェリムスキーからのこのコメントはここに関連しているようです:
TDDには古いガイドラインがあり、テストが傷ついた場合は通常、設計上の問題があるため、テストに耳を傾ける必要があることを示唆しています。テストはテスト対象のコードのクライアントであり、テストが問題になる場合は、コードベース内の他のすべてのクライアントも同様です。このようなショートカットは、すぐに貧弱なデザインの言い訳になります。これをするのは痛いはずなので、私はそれを痛みを伴うままにしておきたい 。
このアドバイスに従って、懸念事項を分離するために、最初にコードを2つの別々のメソッドに分割することをお勧めします。
class SomeClass
def new_map(size)
GoogleMap.new(point: model.location.point, size: size)
end
def map_url(size)
new_map(size).map_url
end
end
次に、それらを個別にテストできます。
describe SomeClass do
let(:some_class) { SomeClass.new }
let(:mock_map) { double('map') }
describe "#new_map" do
it "returns a GoogleMap with the correct point and size" do
map = some_class.new_map('300x600')
map.point.should == [1,2]
map.size.should == '300x600'
end
end
describe "#map_url" do
before do
some_class.should_receive(:new_map).with('300x600').and_return(mock_map)
end
it "initiates a new map of the right size and call map_url on it" do
mock_map.should_receive(:map_url)
some_class.map_url('300x600')
end
it "returns the url" do
mock_map.stub(map_url: "http://www.example.com")
some_class.map_url('300x600').should == "http://www.example.com"
end
end
end
結果のテストコードは長くなり、2つではなく3つの仕様がありますが、コードに含まれるステップをより明確かつ明確に分離し、メソッドの動作を完全にカバーしていると思います。これが理にかなっているかどうか教えてください。
だから、これは私がそれをした方法です、それはこのようにそれをあざけるのは非常に結合されていて壊れやすいと感じます。提案?
describe SomeClass do
let(:some_class) { SomeClass.new }
describe "#map_url" do
it "should instantiate a GoogleMap with the correct args" do
GoogleMap.should_receive(:new).with(point: [1,2], size: '300x600') { stub(map_url: nil) }
some_class.map_url('300x600')
end
it "should call map_url on GoogleMap instance" do
GoogleMap.any_instance.should_receive(:map_url)
some_class.map_url('300x600')
end
end
end