ソフトウェアテストでstubを使うコストを考える(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社エンジニアの「?」を「!」に。原著者の許諾を得て翻訳・公開いたします。Nesteryukソフトウェアテストでstubを使うコストを考える(翻訳)本記事では、テストで何かと見かけるstubについて考察します。この話題は私が働いているチームではすっかり落ち着いていたのですが、最近になってまた話題にのぼったので、この際私の考えをざっくりここにまとめることにしました。誤りや見落としがありましたらぜひお知らせください。私は、依存をstubするのが大好きな開発者でした。テストが簡単に書けますし、読みやすく、しかもシンプルです。endend上のコードから、feeは、それに関連する特定の1人のcustomerまたは1つのcompanyについて定義できることがわかります。依存をstubする(実際はメソッドの呼び出しですが)テストは次のようになります。endendcustomerやcompanyのfeeを設定する代わりに、単にCustomer#order_feeの結果をstubで塞いでいます。「そこは本物のcustomerオブジェクトじゃなくてstubオブジェクトを使うんじゃね?」とツッコまれそうですね。テストをここに書くendしかしあまり変わり映えしません。ひとつの方法は、本物のメソッドの呼び出しを持つ本物のオブジェクトを使うことです。endend私が好きなのは最後のアプローチなので、このままこのテストを使おうと思います。すると「テストで網羅できてないケースがあるよね?feeがcompanyから継承されるケースもテストしなきゃ」とツッコまれるかもしれません。おっしゃるとおりです。Customer#order_feeの部分はCustomerクラス用に書いたテストでカバーされているのですから、同じことを繰り返す理由はありません。ここで第3のケースがCustomer#order_feeに追加されたら、あなたならOrder#totalのテストに戻って新しいケースのテストをまたひとつ追加しますか?stubを使うテストの方がよいと思うでしょう。見た目にも簡単ですし、Customer#order_feeの戻り値に注目すればよく、依存で何が発生するかを気にする必要もありません。test?)も必要になる点です。したテストがないと、単体テストがgreenになっても本番のコードが失敗します。私たちの結合テストで扱う操作に8つのオブジェクトが関わっているとしましょう。これを保証するには、正常に動作するコードでこれらすべてのオブジェクトにアクセスするようになっていなければなりませんが、これらのオブジェクトですべてのケースをカバーするテストが必要ということではありません。Customer#order_feeで実際のcompanyオブジェクトにアクセスするような結合テストがない場合、そのcompanyオブジェクトでcustomerオブジェクトが正常に動作するという証拠もないということになります。したがって、オブジェクトをstubすれば単体テストはシンプルになりますが、その分結合テストが複雑になります。Customerクラスに新しく依存が追加されたときに、あなたなら結合テストをチェックして新しい依存がcustomerオブジェクトで正常に機能するようにしますか?単体テストで実際のオブジェクトを使っておけば、オブジェクトが依存性と協調して動作することはチェック済みになるので、信頼性が高まります。これも銀の弾丸ではありません。依存のケースの取りこぼし最新のテストセットの「ケースの取りこぼし」の話題に戻りましょう。前述のとおり、Customer#order_feeのすべてのケースをOrder#totalのテストでカバーするのはたぶんおかしいでしょう。そのテストはCustomer#order_feeのケースのテストではなくOrder#totalのテストであり、orderオブジェクトとcustomerオブジェクトの協調動作を確認するためのものだからです。したがって、操作に関連するオブジェクトの一部を結合テストで取りこぼしてしまうと、本番のバグをキャッチできる可能性が下がってしまいます。オブジェクトのやりとりは単体テストで既にカバーされているからです。次のコードはもっと違う実装にできるのではないかとツッコまれるかもしれませんが、テストはパスします。endend確かにテストはパスしますが、subtotalが121に等しい場合にもテストはパスしてしまいます(テスト対象のオブジェクトの設定で何かしくじったのかもしれませんね)。先にテストを作成/変更してからコードの作成や変更を行う)を行う理由のひとつです。それもこれも信頼性のためです。Customer#order_fee用に書かれたテストを信頼しないのであれば、ActiveRecord#save!は信頼できるでしょうか?ActiveRecord#save!を使うときに、あなたなら以下のケースをテストしますか?DB接続がない場合にエラーをraiseするテーブルが存在しない場合にエラーをraiseするフィールドが存在しない場合にエラーをraiseするActiveRecord#save!内部のあらゆる部分コードを書くときには常にメンテナンスコストにも気を遣う必要があります。そのコストはいずれ誰かが払わなければなりません。結合テストでは多数のオブジェクトが関連するので、関連するいくつかのオブジェクトへのアクセスパスを見落とす可能性がうんと高まります。繰り返しますが、バグの可能性が高まれば誰かがバグ修正のコストを支払わなければならなくなります。バグを見逃せば損害が発生することをどうかお忘れなく。営業チームにとっていい迷惑です(もちろんあなたにとっても)。stubを使う意味がある場合stubはどんな場合にも避けるべきであると言いたいのではありません。特定の状況では非常に有用です。SinonJsをご存知でしょうか。このライブラリはAjaxレスポンスをstub化できます。}]']);これはstubのユースケースとして完璧です。低レベルな部分をstubしているので、コードのさまざまなレイヤにわたるリクエストのテストを実行すれば、コードが本番でちゃんと動作することを検証できます。ライブラリを書くときにも、ライブラリの依存をstubできます。gemはこのアプローチのよい適用例です。テストを高速にするstubテストが高速になるという理由でstubを好む人もいます。普通ならテストが遅くなったときに「プロジェクトでパフォーマンスの問題が発生してるぞ」と誰かが気づく可能性がありますが、stubを使うとテスト中のパフォーマンス低下が隠蔽されてしまいます。まとめコーディング上のこだわりはこの際抜きにして、作業しているコードの一貫性を保つようにしましょう。ないと、チームに加わった新メンバーが戸惑い、開発速度も落ちてしまいます。コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。enno.jpを運営。実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。Storageを使ってはいけない(翻訳)#Tags#会社紹介#自社サービス#勉強会#Rails#PHP#デザイン#実績#週刊RailsウオッチCONTACTお気軽にご相談ください。(翻訳)CONTACTTechRachoでは、パートナーシップをご検討いただける方からのご連絡をお待ちしております。お気軽にご意見・ご相談ください。