Everyday Rails - RSpecによるRailsテスト入門を読んで

Railsでのテストについて深く学ぶために「Everyday Rails - RSpecによるRailsテスト入門」を読みました。

感想

RSpecを使ったテストの書き方が主な内容で、サンプルのプロジェクト管理アプリにRSpecのテストコードを追加してテストを実行していくという学習の進め方になります。Railsを学んでからテストを学びたいって時におすすめです。ただしテストケースの作り方といったテストの基本的な考え方などは解説されていないので注意です。

とりあえずこの本でモデルスペック、システムスペックを抑えておいて、その他は必要な時に読み直すぐらいでいいんじゃないでしょうか。

難点なのが、日本語訳なので読みづらい部分があるのと説明の中で新しく追加したコードがわかりづらいことがありました。

新しく学んだこと

1章 イントロダクション

  • 筆者のテストの原則
    • 信頼できること
    • 簡単に書けること
    • 簡単に理解できること
  • テストにおけるDRYではないコードは必ずしも悪ではない

2章 RSpecのセットアップ

  • bin/rails generate rspec:installrspecをインストールできる
  • --format documentationでデフォルトのrspecの出力を読みやすいドキュメント形式に変更すると、どのスペックがパスしたか失敗したかがわかりやすくなる
  • 信頼性の高いビューのテストを作ることは大変なのでビューはテストせず、UI関連のテストは結合テストに任せる
  • generateコマンドで作られたファイルの中で使わないものは削除したほうがいい

3章 モデルスペック

  • モデルはアプリのコアとなる部分なので、しっかりテストを行えていれば堅牢なコードになる
  • モデルスペックのベストプラクティス
    • 期待する結果をdescribeでまとめて記述する
    • 1つのitにつき、1つの結果を期待する
    • どのexampleも明示的である
    • exampleの説明は動詞で始まる
  • テスト結果のpendingは保留中という意味
  • 古いshouldではなくexampleを覚える
  • be_validはモデルが有効な状態を理解できているか検証する
  • テストコードが意図した通りに動いているかどうか確認するためにテストが失敗することを検証する
    • toto_notに変えて期待する結果を反転する
    • アプリ側のコードを失敗するように変更する
  • テストを書いている最中にバリデーションについて考えることで書き忘れをなくす
  • rspecで等値のエクスペクテーションを書くときは=ではなくeqを使う
  • be_emptyで値が空であるか確認する
  • describecontextを使ってスペックを整理する
    • describeはクラスやシステムの機能に関するアウトラインを記述
    • contextは特定の状態に関するアウトラインを記述
  • beforeブロックは各テストが実行される前に実行される
  • テストではDRYであるより読みやすいことの方が重要

4章 意味のあるテストデータの作成

  • テストデータを作成するにはフィクスチャ、FactoryBotなどを使う
  • フィクスチャ
    • デフォルトで使える
    • YAML形式でデータを指定する
    • 動作が速い
    • データをデータベースに読む込むときにActiveRecordを使わないのでバリデーションが無視されてしまう
  • FacotryBot
    • インストールが必要
    • 多用するとテストが遅くなる
    • FactoryBot.createでデータをデータベースに保存する
    • FactoryBot.buildでデータをメモリに保存する
    • associationでテーブルの関連を扱う
    • データの定義が重複するときは入れ子にすることで継承し簡潔に書ける
    • パフォーマンス面から可能な限りcreateよりbuildを使う
    • FactoryBotを使うとテストが遅くなる場合もあるので注意

5章 コントローラスペック

  • コントローラのテストは壊れやすいので、コントローラのテストは削除するか、単体テストか統合テストに置き換えることが推奨されている
  • レガシーなアプリではコントローラのスペックを見かけることもあるので学んでおく
  • コントローラ自体が退屈なのでコントローラのテストも退屈であるべき
  • コントローラの責務の1つに適切なフォーマットでデータを返すという役割があるので、html以外の形式のデータもテストする
  • コントローラのテストは認可されていないユーザーとゲストユーザーに対してのアクセス制御ができているかを確認することに限定する

6章 システムスペックでUIテストをする

  • モデルやコントローラが他のモデルやコントローラと一緒にうまく動作するか確認するのがシステムスペック
  • システムスペックの構造はモデルやコントローラと似ている
  • システムスペックの前はフィーチャスペックが使われていた
  • システムスペックではブラウザの操作をシミュレートするCapybaraというgemを使う(Rails5.1以降ならデフォルトでインストールされている)
  • rails g rspec:systemでファイルを作成する
  • Capybaraを使うことで、リンクのクリックやフォームの入力などの操作をテストすることができる
  • コントローラスペックはUIを無視して直接コントローラのメソッドにパラメータを送信するが、システムスペックはアプリの利用者と全く同じUIを使ってパラメータを送信する
  • システムスペックでは複数のエクスペクテーションを書いたり、テストの途中でエクスペクテーションを書くのは問題ない
  • Capybaraが使うドライバはJavaScriptの実行はサポートしていない
  • Seleniumはテストの実行に時間がかかるので、JavaScriptのテストは必要な時だけ有効にする
  • JavaScriptのテストを行うときは、scenariojs: trueを指定する
  • フィーチャスペックよりシステムスペックを使う

7章 リクエストスペックでAPIをテストする

  • JSONの出力はコントローラスペックでもできるが、堅牢なAPIを構築するにはリクエストスペックが必要
  • リクエストスペックではCapybaraを使わず、代わりにHTTPに対応するメソッドを使う
  • コントローラスペックと異なる点はルーティング名を自由に決めれること

8章 スペックをDRYに保つ

  • テストの重複をなくすためにモジュールを使うができる
  • beforecontextではなく、letを使うことで使いたい時だけデータを読み込むことができる
  • let!で遅延読み込みせずにブロックを即座に実行できる
  • テストを行うときはなるべく余計なデータをもたないようにするが、可読性にも気を付ける
  • 自分でマッチャを作ることもできるが、マッチャの定義や、エラー時の出力など管理するコードも増えるので注意
  • 失敗するエクスペクテーションが実行されると即座に停止して失敗を報告し、残りのエクスペクテーションは実行されない
  • aggregate_failuresを使って失敗を集約することで、失敗した複数のエクスペクテーションを把握することができる
  • システムスペックの長いコードをメソッドに切り分けてまとめることで可読性を高くできるが、使いすぎるとヘルパーメソッドが何をやって何を検証しているかを理解するためにテストスイートの中身を確認しなければならなくなるので注意する

9章 早くテストを書き、速いテストを書く

  • まず動かし、次に正しくし、速くする
  • いかにスペックの実行時間とテストの作成時間を速くするか
  • モックはオブジェクトの代わりをするもので、データベースにアクセスしないのでテストの時間が短くなる
  • スタブはオブジェクトのメソッドをオーバーライドして決められた値を返すダミーのメソッド
  • モックやタグはテストの実行時間を減らすためのもの
  • テストスイートにfocus: true doと記述することで、実行したいテストだけ実行できる
  • タグはコミット前に全て消し、機能が完成したら必ずタグなしでテストを実行する
  • 不必要なテストはコメントアウトするよりskipを使うことで、テストを実行したときにスキップしたことが表示される
  • テストが遅い時は並列に実行することで速くできる

10章 その他のテスト

  • テストのためのアップロードしたファイルは自動的に削除されるように設定しておく
  • ファイルのアップロードをテストするときは、スペックファイルを作成→テスト用のアップロードパスを設定→テストが終わったらファイルを削除するように設定の3つのステップを行う
  • 何度もファイルをアップロードする場合はファイルをモデルの属性に含めることも検討する
  • rspec-railsではバックグラウンドジョブをテストするにはActiveJob::Base.queue_adapter = :testを指定する
  • メール送信はメッセージが正しく構築されているか、正しい宛先に送信されるかをテストする
  • メールはバックグラウンドプロセスで送信される
  • メールの詳しいテストはMailerのスペックに書き、正しく送信できているかはシステムスペックに書く
  • 外部のサービスを直接テストすると、テストが遅くなったり、実際に料金が発生してしまう場合があるので、vcrのようなツールを使う

12章 最後のアドバイス

  • 小さいテストで練習する
  • モデルスペックからシステムスペックまでテストを進めることに慣れたら、逆からテストを書いてみる
  • テストのメリットを他の開発者に売り込む