RailsエンジニアがRSpecを使ったテストを書き始める前に知っておきたい、
そもそも何をテストすべきかについて考えるための用語、テストでよく遭遇する問題、スタイルガイド等のリンクについてまとめました。
ソフトウェアテスト用語について
1. テストレベル
1-1. 単体テスト(Unit tests)
- ソフトウェアの単体のモジュールのテスト(例: クラスやメソッドなど)
- RSpec では Model Spec などに相当する。
1-2. 結合テスト(Integration tests)
- ソフトウェアの複数のモジュールのテスト(例: コントローラーのアクションやAPIなど)
- RSpec では Request Spec に相当する。
1-3. E2Eテスト(System tests)
- 利用者によるブラウザ操作のテスト
- RSpec では System Spec に相当する。
1-4. 受け入れテスト(UAT / User Acceptance tests)
- ソフトウェア開発プロセスの最終段階で行われるテストであり、開発されたシステムが実際のユーザーの要件と期待を満たすかどうかを確認するためのテスト。
- 実際のユーザーやクライアントによって行われる。
○○テストまとめ(テストレベル/テストタイプ/テスト技法) - Zenn
2. テストタイプ
2-1. ブラックボックステスト
- システムの内部構造や動作を考慮せず、ソフトウェアを外部から見たときの動作だけをテストする。
- RSpecでは、Capybaraを使用したE2Eの自動テストに相当する。
2-1-1. ブラックボックステストのカバー範囲による分類
-
機能テスト
- ソフトウェアが設計要件を満たすかどうかのテスト。
-
非機能テスト
- ソフトウェアのパフォーマンス、信頼性、安定性など、システムの特性に焦点を当てたテスト。
2-1-2. ブラックボックステストの設計テクニックの種類
-
境界値テスト
- 入力パラメータの有効範囲の境界値のテスト。
- 入力の最小値、最大値、ちょうど中間の値などを対象にする。
-
等価クラステスト
- 入力値を同等と見なせる範囲(等価クラス)に分け、各クラスから代表値を選ぶテスト。
- テストケースの数を効率的に減らすことができる。
-
決定テーブルテスト(ディシジョンテーブルテスト)
- 全ての条件とその組み合わせを一覧表にまとめ、それに基づいてテストケースを作成し行われるテスト。
- 入力や出力が多く、それらが複雑に関連している場合に用いられる。
-
状態遷移テスト
- 状態遷移図を使ってテストケースを設計し行われるテスト。
- ソフトウェアが特定の状態から別の状態に遷移するかどうかをテストする。
テスト技法 ブラックボックステストの概要と設計手法について - Zenn
デシジョンテーブル(意思決定表)とは?作り方3ステップと分かりやすく整理する方法 - Qbook
2-2. ホワイトボックステスト(構造テスト)
- 内部のコードや構造に焦点を当て、正しく動作しているかをテストする。
- RSpecでは、モデルのメソッドなどのテストに相当する。
2-2-1. ホワイトボックステストの種類
- 単体テスト
- 統合テスト、など
2-3. 回帰テスト(リグレッションテスト)
- 新しいコードの変更や機能の追加が既存の機能に悪影響を及ぼさないことを確認するためのテスト。
リグレッションテスト(回帰テスト)とは|目的や重要性、実施のポイント - SHIFT ASIA
3. テスト手法のテクニック
モック(テストダブル)とは
「モック」と言うとき、テスト内で実際のオブジェクトやサービスの代わりに使用されるオブジェクトのことを指すことが多い。
テストダブルとは、実際のオブジェクトを模倣(ダブル)する一般的な用語。
以下、テストダブルの種類。
スタブ
テスト対象への間接入力を提供する。
一部の固定の値を返すようにプログラムされたシンプルな実装で、特定のシナリオをテストするために使用される。
スパイ
スタブと同様に固定の値を返すが、特定のメソッドがどのように呼び出されたかを記録する。これにより、テスト中に特定の動作が行われたかを確認できる。
モック(モックオブジェクト)
テスト対象からの間接出力を検証する。
特定の予期された動作を記録し、それが適切に行われたかを検証する。
モックは通常、テストケース内で期待値を設定し、その期待値が満たされたかを検証する。
ダミーオブジェクト
コンパイルエラーを回避したり、メソッドの引数として使用される。実際のテストには影響を及ぼさない。
フェイクオブジェクト
通常は時間がかかるか、複雑な操作を行うオブジェクトの軽量な代替品。
例えば、データベースのフェイクオブジェクトは、メモリ内のコレクションを使用して、実際のデータベース接続をシミュレートすることがある。
これで迷わないテストダブルの分類(ダミー、スタブ、スパイ、モック、フェイク) - Qiita
例
※ 実際には、テスト環境からは叩けないAPIのエンドポイントへのリクエストとレスポンスをモック化することが多い気がする。
context 'postがuserを返すこと' do
user = double('User') # これがスタブ
post = Post.new
allow(post).to receive(:user).and_return(user) # これがモック
let!(:user1) { create(:user) }
it { expect(post.user).to eq user1 }
end
4. テストにおける問題
flakey test(ランダム落ち)
- 多くの開発現場ではCIパイプラインにテストが組み込まれていることが多いが、ランダムに失敗する時と成功する時があるようなテストが出現する場合があり、このようなテストを flakey test と呼ぶ。
- 発生ケースをざっくり書くと、E2EのテストでDOM要素が現れるのを待つことができていないことが原因であることが多い。
- flakey test対応のアンチパターン
-
sleep 1
などでの対処。
-
- ベストプラクティス
- Capybaraの適切なマッチャを使用する。
- JavaScriptによるDOM操作が原因である場合は、
wait_for_ajax
のようなメソッドを作成・使用する。 - こちらのリンクも詳しい。
5. テスト戦略
TDD
- Red/Green/Refactor = 失敗/成功/リファクタリング
- テスト駆動開発 - Wikipedia
- テストを先に書き、テストが通るコードを書き、その後リファクリングするような開発の流れ。
- TDD is dead. Long live testing. - By DHH ※邦訳で、Unit tests(単体テスト)ファーストのアプローチは良いシステム設計に影響するとは限らず、System tests(システムテスト)の重要度を上げよう(ただし全てをSystem testsにしないこと)という主張がされている。
- TDD Is Fun - by Peter Solnica ※邦訳で、TDD is dead.のアンサーとして、単体テストを(上位のテストが存在していなかったり、プロトタイピングのときから)あまりに早くに書いたり、モックやスタブを使う段階ではないのに使っていることが悪いのであり、TDDや単体テスト自体は楽しいものである、という主張がされている。
BDD
- Given/When/Then = 状態/振舞/変化
- GivenWhenThen - martinfowler.com
- RSpecに置き換えると、describe/context/it みたいなイメージがある。
AAA
- Arrange/Act/Assert = 準備/実行/検証
- Arrange Act Assert - WikiWikiWeb
- RSpecに置き換えると、let,before,subject/it/expect みたいなイメージがある。
テストピラミッド
- より結合度の高いテスト(System SpecやRequest Spec)を多く書けば実行時間は長くなり、孤立したモジュールのテスト(Model Spec)が多いほど実行時間は短くなる。
テスティングトロフィー
- フロントエンドのテストで支持される考え方らしい。
- トロフィーが上に行くほど、障害点が多くなるためテストが失敗する可能性が高く、テストの分析と修正に必要な時間が長くなり、実行されるコードが増えるため、速度が低下する。一方、「信頼係数」が増える。
テストコードのないRailsアプリケーションにRSpecを導入する時の個人的方針
- まずは結合度の高い System Spec から書き始めれば、少ない工数で品質を高められると思う。
- 一方、全て結合度の高いテストで担保しようとするとテストの実行時間が長くなるため、System Spec から切り出せそうな処理はそれぞれの Spec として書く。
- コントローラ(API)に対しては Request Spec を書いて、Railsアプリケーションの規模やアーキテクチャによって、各クラスの単体テストに当たる、Model Spec やら Form Spec , Service Spec , Mailer Spec などを書いていくとよいと思う。
リンク集
- rspec/rspec-rails - GitHub
- RSpec - Relish
- 使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita
- 使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita
- 使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita
システムテスト関連
Capybara
Headlessドライバ
Selenium
Cuprite
- rubycdp/cuprite
- 2020年のRailsでブラウザテストを「正しく」行う方法(翻訳) - TechRacho
- 2021年6月現在、Cupriteで"正しい"システムテストはできるのか? - Zenn
スタイルガイド
- willnet/rspec-style-guide
- Clean Test Code Revised - Speaker Deck
- Better Specs
- 【初心者向け】レビュワーをイライラさせるRSpec集と解決方法 - メドピア開発者ブログ
- テストコードの期待値はDRYを捨ててベタ書きする ~テストコードの重要な役割とは?~ - Qiita
- 【アンチパターン】Arrange、Act、Assert(AAA)を意識できていないRSpecのコード例とその対処法 - Qiita
- 現場で使えるRSpecパターン集 for Rails App - mogulla3.tech
- RSpec では context 間の違いを表現するときにのみ let を使う - id:onk のはてなブログ
Factorybot
亜流
- rspecのrspecに学ぶ、ネストの深いrspecを書かない方法 - Qiita
- 俺のRSpecがこんなに雑なわけがない - Qiita
- サヨナラBetter Specs!? 雑で気楽なRSpecのススメ - Qiita