テストには 2 つのまったく異なるアプローチがあるようですが、両方を挙げたいと思います。
問題は、それらの意見は 5 年前 (2007 年) に述べられたものであり、私は興味があります。それ以来、何が変わったのか、どの方向に進むべきなのかということです。
理論的には、テストは実装にとらわれないはずです。これにより、脆弱なテストが少なくなり、実際に結果 (または動作) がテストされます。
RSpec では、モデルを完全にモックしてコントローラーをテストするという一般的なアプローチは、コントローラーの実装を詳しく調べる必要があるように感じます。
これ自体はそれほど悪いことではありませんが、問題は、モデルの使用方法を決定するためにコントローラーをあまりにも詳しく調べていることです。コントローラが Thing.new を呼び出すかどうかが問題になるのはなぜですか? コントローラーが Thing.create を使用することにした場合はどうなりますか? そして救出ルート?モデルに Thing.build_with_foo のような特別な初期化メソッドがある場合はどうなりますか? 実装を変更しても、動作の仕様が失敗することはありません。
ネストされたリソースがあり、コントローラーごとに複数のモデルを作成している場合、この問題はさらに悪化します。私のセットアップ方法のいくつかは、最終的に 15 行以上の長さになり、非常に壊れやすくなります。
RSpec の意図は、コントローラー ロジックをモデルから完全に分離することです。これは理論的には良さそうに思えますが、Rails のような統合スタックの粒度にほとんど反します。特にスキニー コントローラー/ファット モデルの規律を実践すると、コントローラー内のロジックの量が非常に少なくなり、セットアップが膨大になります。
では、BDD 志望者は何をしたいのでしょうか? 一歩戻って、私が実際にテストしたい動作は、コントローラーが Thing.new を呼び出すことではなく、パラメーター X を指定すると、新しいモノを作成してリダイレクトすることです。
デビッド・チェリムスキー:
それはすべてトレードオフに関するものです。
AR が委譲ではなく継承を選択するという事実は、私たちをテスト バインドに陥らせます。つまり、データベースに結合するか、実装とより親密になる必要があります。表現力とドライさのメリットを享受するため、このデザインの選択を受け入れます。
このジレンマに対処するために、私は少し壊れやすいという代償を払って、より高速なテストを選択しました。実行速度がわずかに遅くなるという犠牲を払って、脆弱性の低いテストを選択しています。どちらにしてもトレードオフです。
実際には、1 日に数千回とは言わないまでも数百回テストを実行し (autotest を使用し、非常に細かい手順を実行します)、「new」または「create」のどちらを使用するかを変更することはほとんどありません。また、段階が細かいため、登場する新しいモデルは最初は非常に不安定です。valid_thing_attrs アプローチは、この問題を少し軽減しますが、新しい必須フィールドごとに valid_thing_attrs を変更する必要があることを意味します。
しかし、あなたのアプローチが実際にあなたのために働いているなら、それは良いことです! 実際、好きな方法でサンプルを生成するジェネレーターを備えたプラグインを公開することを強くお勧めします。多くの人がその恩恵を受けると確信しています。
好奇心から、テスト/仕様でどのくらいの頻度でモックを使用しますか? おそらく私は何か間違ったことをしているのですが、それが非常に制限的であることがわかりました。1 か月以上前に rSpec に切り替えて以来、コントローラーとビュー レイヤーがデータベースにまったくアクセスせず、モデルが完全にモック アウトされているドキュメントで推奨されていることを実行してきました。これにより、速度が大幅に向上し、いくつかのことが簡単になりますが、これを行うことの短所が長所をはるかに上回っていることがわかりました. モックを使用して以来、私のスペックはメンテナンスの悪夢に変わりました。仕様は、実装ではなく動作をテストするためのものです。メソッドが呼び出されたかどうかは気にしません。結果の出力が正しいことを確認したいだけです。モッキングは実装に関して仕様をうるさくするため、単純なリファクタリングを行います (それは 動作を変更することはできません) 常に仕様に戻って「修正」する必要がなければ不可能です。私は、仕様/テストが何をカバーすべきかについて非常に意見があります。テストは、アプリが中断したときにのみ中断する必要があります。これは、私がビュー レイヤーをほとんどテストしない理由の 1 つです。ビュー内の小さな変更を行うと、アプリが壊れることなくテストが壊れることがよくあります。私はモックで同じ問題を見つけています。これらすべてに加えて、今日、クラスメソッドのモック/スタブが (場合によっては) 仕様間で固執していることに気付きました。仕様は自己完結型であり、他の仕様の影響を受けないようにする必要があります。これはその規則を破り、トリッキーなバグにつながります。このすべてから私は何を学びましたか?モッキングを使用する場所に注意してください。スタブはそれほど悪くはありませんが、同じ問題がいくつかあります。
過去数時間かけて、スペックからほぼすべてのモックを削除しました。また、コントローラーの仕様で「integrate_views」を使用して、コントローラーとビューの仕様を 1 つにマージしました。また、各コントローラー仕様のすべてのフィクスチャをロードしているため、ビューを埋めるためのテスト データがいくつかあります。最終結果は?私の仕様は短く、シンプルで、一貫性があり、柔軟性が低く、スタック全体 (モデル、ビュー、コントローラー) を一緒にテストするので、バグが隙間をすり抜けることはありません。これが誰にとっても「正しい」方法だと言っているわけではありません。あなたのプロジェクトが非常に厳密な仕様のケースを必要とする場合、それはあなたのためではないかもしれませんが、私の場合、これは私がモックを使用する前に持っていたものよりも優れています. いくつかの場所ではスタブが良い解決策だと今でも思っているので、まだそうしています。