1. イントロダクション
Ruby on Railsと自動テストは相性の良い組み合わせです。Railsにはデフォルトのテストフレームワークが付いてきます。ジェネレータを動かせば自動的にひな型となるテストファイルも作られるので、すぐに自分自身のテストコードを書き込むことができます。とはいえ、Railsでテストを全く書かずに開発する人や、書いたとしても大して役に立たない、もしくは書いてもほとんど意味のないスペックをちょこっと書いて終わらせるような人もたくさんいます。
これにはいくつかの理由があると私は考えています。人によってはRubyや規約の厳しいwebフレームワークを覚えることだけで精一杯になってしまい、そこへさらに新しい技術が増えるのは 余計な仕事 としか思えないのかもしれません。もしくは時間の制約が問題になっている可能性もあります。テストを書く時間が増えることによって、顧客や上司から要求されている機能に費やす時間が減ってしまうからです。もしくはブラウザのリンクをクリックするのが テスト であるという習慣から抜け出せなくなっているだけかもしれません。
私も同じでした。私は自分のことを正真正銘のエンジニアだとは思ったことはありませんが、解決すべき問題を持っているという点ではエンジニアと同じです。そしてたいていの場合、ソフトウェアを構築する中でそうした問題の解決策を見つけています。私は1995年からwebアプリケーションを開発しており、予算の乏しい公共セクターのプロジェクトを長い間一人で担当しています。小さいころにBASICをさわったり、大学でC++をちょっとやったり、社会人になってから入った2社目の会社で役に立たないJavaのトレーニングを一週間ほど受講したりしたことはありましたが、ソフトウェア開発のまともな教育というものは全く受けたことがありません。実際、私は2005年までPHPで書かれたひどいスパゲティプログラムをハックしていて、それからようやくwebアプリケーションのもっと上手な開発方法を探し始めました。
私はかつてRubyを触ったことはありましたが、真剣に使い始めたのはRailsが注目を集め出してからです。RubyやRailsには学習しなければいけないことがたくさんありました。たとえば、新しい言語や アーキテクチャ 、よりオブジェクト指向らしいアプローチ等々です(Railsにおけるオブジェクト指向を疑問に思う人がいるかもしれませんが、フレームワークを使っていなかったころに比べれば、私のコードはずっとオブジェクト指向らしくなりました)。このように新しいチャレンジはいくらか必要だったものの、それでもフレームワークを使わずに開発していた時代に比べると、ずっと短い時間で複雑なアプリケーションが作れました。こうして私は夢中になったのです。
とはいえ、Railsに関する初期の書籍やチュートリアルは、テストのような良いプラクティスよりも開発スピード(15分でブログアプリケーションを作る!)にフォーカスしていました。テストの説明は全くなかったか、説明されていたとしても、たいてい最後の方に一章だけしか用意されてませんでした。最近の書籍やweb上の情報源ではその欠点に対処しており、アプリケーション全体をテストする方法を初めから説明しているように思います。加えて、テストについて 専門的に 書かれた本はたくさんありますが、テストの実践方法をしっかり身につけていないと開発者(特にかつての私と同じような立場の開発者)の多くは一貫したテスト戦略を考えられないかもしれません。もしテストが多少あったとしても、そのようなテストは信頼できるものではなかったり、あまり意味のないテストだったりします。そんなテストでは 自信をもって開発する ことはできません。
本書の第一のゴールは 私の 役に立っている一貫した戦略をあなたに伝えることです。そしてその戦略を使って、 あなたも 一貫したテスト戦略をとれるように願っています。私の戦略が正しく、本書でそれをうまく伝えることができれば、あなたは 自信をもってテストが書けるようになります。 そうすればコードに変更を加えるのも簡単になります。なぜなら、テストコードがアプリケーションをしっかり守り、何かがおかしくなったらあなたにすぐ知らせてくれるからです。
なぜRSpecなのか?
誤解がないように言っておきますが、私はRuby用の他のテストフレームワークを悪く言うつもりはありません。実際、単体のRubyライブラリを書くときはMiniTestをよく使っています。しかし、Railsアプリケーションを開発し、テストするときはRSpecを使い続けています。
私にはコピーライティングとソフトウェア開発のバックグラウンドがあるせいかもしれませんが、RSpecを使うと読みやすいスペックが簡単に書けます。これが私にとって一番の決め手でした。のちほど本書でお話ししますが、技術者ではなくても大半の人々がRSpecで書かれたスペックを読み、その内容を理解できたのです。RSpecを使って自分のソフトウェアの期待する振る舞いを記述することは、もはや私の習慣になってしまったと言ってよいでしょう。RSpecの構文はスラスラと私の指先から流れ出してきます。そして、将来何か変更を加えたくなったときでも、相変わらず読みやすいです。
本書の第二のゴールは日常的によく使うRSpecの機能と構文をあなたが使いこなせるように手助けすることです。RSpecは複雑なフレームワークです。しかし、多くの複雑なシステムがそうであるように、8割の作業は2割の機能で済ませられるはずです。なので、本書はRSpecやCapybaraのような周辺ライブラリの完全ガイドにはなっていません。そのかわり、Railsアプリケーションをテストする際に私が何年にもわたって使ってきたツールに焦点を絞って説明しています。また、本書ではよくありがちなパターンも説明します。本書では具体的に説明していない問題に遭遇したときも、あなたがこのパターンをちゃんと理解していれば、長時間ハマることなく、すぐに解決策をみつけることができるはずです。
対象となる読者
もしRailsがあなたにとって初めてのwebアプリケーションだったり、これまでのプログラマ人生でテストの経験がそれほどなかったりするなら、本書はきっと良い入門書になると思います。もし、あなたが 全くの Rails初心者なら、この「Everyday Rails - RSpecによるRailsテスト入門」を読む前に、Michael Hartlの Railsチュートリアル や、Daniel Kehoeの Learn Ruby on Rails 、もしくはSam Rubyの RailsによるアジャイルWebアプリケーション開発 といった書籍でRailsの開発や基礎的なテスト方法を学習しておいた方が良いかもしれません。なぜなら、本書はあなたがすでにRailsの基礎知識を身につけていることを前提としているからです。言い換えると、本書ではRailsの使い方は説明しません。また、Railsに組みこまれているテストツールを最初から紹介することもしません。そうではなく、RSpecといくつかの追加ライブラリをインストールします。追加ライブラリはテストのプロセスをできるだけ理解しやすく、そして管理しやすくするために使います。というわけで、もしあなたがRails初心者なのであれば、まず先ほどの資料を読み、それから本書に戻ってきてください。
もしあなたがRailsの開発経験は多少あるものの、テストにはまだ馴染めていない開発者なのであれば、まさに本書は最適です!私もかつてはずっとあなたと同じでしたが、私は本書で紹介するようなテクニックでテストカバレッジを向上させ、テスト駆動開発者らしい考え方を身につけることができました。あなたも私と同じようにこうしたテクニックを身につけてくれることを私は願っています。
ところで、本書で前提としている読者の知識や経験を具体的に挙げると、次のようになります。
- Railsで使われているサーバーサイドのModel-View-Controller規約
- gemの依存関係を管理するBundler
- Railsコマンドの実行方法
- リポジトリのブランチを切り替えられるぐらいのGit知識
もしあなたがTest::UnitやMiniTest、もしくはRSpecそのものに慣れていて、自信をもって開発できるワークフローを確立している場合、本書を読むことでテストのアプローチを多少改善できるかもしれません。私なりのテストのアプローチから、単にコードをテストするだけでなく、意図をもってテストする方法を学んでいただければ、と思います。
本書はテスト理論に関する本 ではありません。 また、長年使われてきたソフトウェアにありがちなパフォーマンス問題を深く掘り下げるわけでもありません。本書を読むよりも、他の書籍を読んだ方が役立つかもしれません。本書の巻末にある「Railsのテストに関するさらなる情報源」を参考にしてください。このページではここで紹介した資料や、その他の書籍、Webサイト、テスト関連のチュートリアルのリンクを載せています。
私が考えるテストの原則
どういった種類のテストが一番良いのですか?単体テストですか?それとも統合テストですか?私はテスト駆動開発(TDD)を練習すべきでしょうか?それとも振る舞い駆動開発(BDD)を練習すべきですか?(そして両者の違いは何ですか?)私はコードを書く前にテストを書くべきでしょうか?それとも、コードのあとに書くべきでしょうか?そもそもテストを書くのをサボってもいいのでしょうか?
Railsをテストする 正しい 方法というテーマで議論をすると、プログラマの間で大げんかが始まるかもしれません。まあ、Mac対PCや、Vim対Emacsのような論争ほど激しくないとは思いますが、それでもRubyistの中で不穏な空気が流れそうです。実際、David Heinmeier-HansenはRailsconf 2014でTDDは「死んだ」と発言し、Railsのテストについて近年新たな議論を巻き起こしました。
確かにテストの正しい方法は存在します。しかし私に言わせれば、テストに関してはその 正しさ の度合いが異なるだけです。
私のアプローチでは次のような基本的な信条に焦点を当てています。
- テストは信頼できるものであること
- テストは簡単に書けること
- テストは簡単に理解できること(今日も将来も)
つまり、テストはあなたに開発者としての 自信 を付けさせるものであるべきなのです。この三つの要素を意識しながら実践すれば、アプリケーションにしっかりしたテストスイートができあがっていきます。もちろん、あなたが本物のテスト駆動開発者に近づいていくことは言うまでもありません。
一方、それと引き替えに失うものもあります。具体的には次のようなことです。
- スピードは重視しません。ただし、これについてはのちほど説明します。
- テストの中では過度にDRYなコードを目指しません。なぜならテストにおいてはDRYでないコードは必ずしも悪とは限らないからです。この点ものちほど説明します。
とはいえ結局、一番大事なことは テストが存在すること です。信頼性が高く、理解しやすいテストが書いてあることが大事な出発点になります。何から何まで完璧である必要はありません。かつて私はたくさんアプリケーション側のコードを書き、ブラウザをあちこちクリックすることで “テスト” し、うまく動くことを祈っていました。しかし、前述したアプローチによって、こうした問題をついに乗り越えることができました。完全に自動化されたテストスイートを利用すれば、開発を加速させ、潜在的なバグや境界値に潜む問題をあぶり出すことができるのです。
そして、このアプローチこそがこれから本書で説明していく内容です。
本書の構成
本書「Everyday Rails - RSpecによるRailsテスト入門」では、標準的なRailsアプリケーションが全くテストされていない状態から、RSpecを使ってきちんとテストされるまでを順に説明していきます。本書ではRails 7.1とRSpec Rails 6.1(RSpec本体のバージョンは3.12)を使用します。これはどちらも執筆時点の現行バージョンです。
本書は次のようなテーマに分けられています。
- あなたが今読んでいるのが第1章 イントロダクション です。
- 第2章 RSpecのセットアップ では、新規、もしくは既存のRailsアプリケーションでRSpecが使えるようにセットアップします。
- 第3章 モデルスペック では、シンプルでも信頼性の高い単体テストを通じてモデルをテストしていきます。
- 第4章 意味のあるテストデータの作成 では、テストデータを作成するテクニックを説明します。
- 第5章 コントローラスペック では、コントローラに対して直接テストを書いていきます。
- 第6章 システムスペックでUIをテストする では、システムスペックを使った統合テストを説明します。統合テストを使えば、アプリケーション内の異なるパーツがお互いにきちんとやりとりできることをテストできます。
- 第7章 リクエストスペックでAPIをテストする では、昔ながらのUIを使わずに直接APIをテストする方法を説明します。
- 第8章 スペックをDRYに保つ では、いつどのようにしてテストの重複を減らすのか、そしていつ、そのままにすべきなのかを議論します。
- 第9章 速くテストを書き、速いテストを書く では、効率的にテストを書くテクニックと、素早いフィードバックを得るために実行対象のテストを絞り込む方法を説明します。
- 第10章 その他のテスト ではメール送信やファイルアップロード、外部のWebサービスといった、これまでに説明してこなかった機能のテストについて説明します。
- 第11章 テスト駆動開発に向けて ではステップ・バイ・ステップ形式でテスト駆動開発の実践方法をデモンストレーションします。
- そして、第12章 最後のアドバイス で、これまで説明してきた内容を全部まとめます。
各章にはステップ・バイ・ステップ形式の説明を取り入れています。これは私が自分自身のソフトウェアでテストスキルを上達させたのと同じ手順になっています。また、多くの章では どう テストし、 なぜ テストするのかをしっかり考えてもらうためのQ&Aセクションで締めくくり、そのあとに演習問題が続きます。この演習問題はその章で習ったテクニックを自分で使ってみるために用意しています。繰り返しますが、あなた自身のアプリケーションでこうした演習問題に取り組んでみることを私は強く推奨します。一つはチュートリアルの内容を復習するためで、もう一つはあなたが学んだことをあなた自身の状況で応用するためです。本書では一緒にアプリケーションを作っていくのではなく、単にコードのパターンやテクニックを掘り下げていくだけです。ここで学んだテクニックを使い、あなた自身のプロジェクトを改善させましょう!
サンプルコードのダウンロード
本書のサンプルコードはGitHubにあります。このアプリケーションではテストコードも完全に書かれています。
もしGitの扱いに慣れているなら(Rails開発者ならきっと大丈夫なはず)、サンプルコードをあなたのコンピュータにクローン(clone)することもできます。各章の成果物はそれぞれブランチを分けています。各章のソースを開くと完成後のコードが見られます。本書を読みながら実際に手を動かす場合は、一つ前の章のソースを開くと良いでしょう。各ブランチには章番号を振ってあります。各章の最初でチェックアウトすべきブランチをお伝えし、現在の章と一つ前の章で発生する変更点を確認できるリンクを紹介します。
本書はどの章も一つ前の章のソースコードに変更を加えていく流れで構成されています。なので、現在の章のスタート地点として一つ前の章を使うことができます。たとえば、第5章のコードを最初から順に入力していきたいのであれば、第4章のコードから書き始めてください。
$ git checkout -b my-05-controllers origin/04-factories
Gitの扱いに慣れていなくても各章のサンプルコードをダウンロードすることは可能です。まずGitHubのプロジェクトページを開いてください。それからブランチセレクタでその章のブランチを選択します。
最後にZIPダウンロードリンクをクリックします。クリックするとソースコードをコンピュータに保存できます。
コードの方針
このアプリケーションは次の環境で動作します。
- Rails 7.1: 最新バージョンのRailsが本書のメインターゲットです。私が知る限り、Rails 5.1以上であれば本書で紹介しているテクニックは適用できるはずです。サンプルコードによっては違いが出るかもしれませんが、差異が出そうな箇所はできる限り伝えていきます。
- Ruby 3.3: Rails 7.1ではRuby 2.7以上が必須です。本書では執筆時点の最新バージョンであるRuby 3.3を使用します。
- RSpec Rails 6.1とRSpec 3.12: RSpecはRails専用の機能を提供するRSpec Rails(rspec-rails)と、RSpecの本体であるRSpec(rspec-core)がそれぞれ独立したgemとしてリリースされています。どちらも本書執筆時点の最新バージョンです。以前はRSpecとRSpec Railsはバージョン番号を統一してリリースされていましたが、RSpec Rails 4.0からバージョン番号は別々に更新されるようになりました。
本書で使用しているバージョン固有の用法等があれば、できる限り伝えていきます。もしRails、RSpec、Rubyのどれかで古いバージョンを使っているなら、本書の以前の版をダウンロードしてください。以前の版はLeanpubからダウンロードできます。個々の機能はきれいに対応しませんが、バージョン違いに起因する基本的な差異は理解できるもしれません。
もう一度言いますが、 本書はよくありがちなチュートリアルではありません! 本書に載せているコードはアプリケーションをゼロから順番に作ることを想定していません。本書ではテストのパターンと習慣を学習し、あなた自身のRailsアプリケーションに適用してもらうことを想定しています。言いかえると、コードをコピー&ペーストすることができるとはいえ、そんなことをしても全くあなたのためにならないということです。あなたはこのような学習方法をZed ShawのLearn Code the Hard Wayシリーズで知っているかもしれません。
「Everyday Rails - RSpecによるRailsテスト入門」はそれと全く同じスタイルではありませんが、私はZedの考え方に同意しています。すなわち、何か学びたいものがあるときはStack Overflowや電子書籍からコピー&ペーストするのではなく、自分でコードをタイピングした方が良い、ということです。
間違いを見つけた場合
私はたくさんの時間と労力をつぎ込んで「Everyday Rails - RSpecによるRailsテスト入門」をできる限り間違いのない本にしようとしてきましたが、私が見落とした間違いにあなたは気付くかもしれません。そんなときはGitHubのissuesページで間違いを報告したり詳細を尋ねたりしてください。 https://github.com/JunichiIto/everydayrails-rspec-jp-2024/issues(訳注: このURLは日本語版専用のissuesページなので、日本語で質問できます。ただし、「サンプルアプリケーションがうまく動かない」といった技術的な質問はこのissuesページではなく、 Teratail のようなプログラマ向けQ&Aサイトで質問してもらえると助かります)
gemのバージョンに関する注意点
本書と本書のサンプルアプリケーションで使用しているgemのバージョンは、このRSpec Rails 6.1/Rails 7.1版を執筆していたとき(2024年1月)の現行バージョンです。当然、どのgemも頻繁にアップデートされますので、Rubygems.orgやGitHub、またはお気に入りのRuby新着情報フィードでアップデート情報をチェックしてください。
サンプルアプリケーションについて
本書で使用するサンプルアプリケーションはプロジェクト管理アプリです。TrelloやBasecampほど多機能でカッコいいものではありませんが、テストを書き始めるためには十分な機能を備えています。
はじめに、このアプリケーションは次のような機能を持っています。
- ユーザーはプロジェクトを追加できる。追加したプロジェクトはそのユーザーにだけ見える。
- ユーザーはタスクとメモと添付ファイルをプロジェクトに追加できる。
- ユーザーはタスクを完了済みにできる。
- 開発者はパブリックAPIを使って、外部のクライアントアプリケーションを開発できる。
私はここまで意図的にRailsのデフォルトのジェネレータだけを使ってアプリケーション全体を作成しました( 01_untested ブランチにあるサンプルコードを参照)。つまりこれは test ディレクトリに何も変更していないテストファイルとフィクスチャがそのまま格納されているということです。この時点で bin/rails test を実行すると、いくつかのテストはそのままでパスするかもしれません。しかし、本書はRSpecの本ですので、testフォルダは用無しになります。RSpecが使えるようにRailsをセットアップし、信頼できるテストスイートを構築していきましょう。これが今から私たちが順を追って見ていく内容です。
まず最初にすべきことは、RSpecを使うようにアプリケーションの設定を変更することです。では始めましょう!
サンプルアプリケーションのセットアップ手順
サンプルアプリケーションを実際に動かす場合は以下の手順でセットアップしてください。
まず、サンプルアプリケーションには以下のツールやソフトウェアが必要です。不足している場合は適宜インストールしてください。
- Ruby 3.3.0(ただし、Ruby 3.0.0以上であれば動作することを確認しています)
- Git
- Google Chrome(執筆時点のバージョンは120)
インストールが済んだらターミナル上で次のコマンドを入力してセットアップします。
# ソースコードのダウンロード
git clone https://github.com/JunichiIto/everydayrails-rspec-jp-2024.git
# ディレクトリの移動
cd everydayrails-rspec-jp-2024
# 使用するRubyバージョンを指定(本書では3.3.0を推奨。下記コマンドはrbenvを使用する場合)
rbenv local 3.3.0
# gemのインストールやデータベースのセットアップ等
bin/setup
# サーバーの起動
bin/rails s
Listening on http://127.0.0.1:3000 のような表示がターミナルに表示されればOKです。 http://localhost:3000 にブラウザでアクセスするとホームページが表示されます。
“Sign up”のリンクを開くとユーザー登録ができ、サンプルアプリケーションを使えるようになります。サーバーを停止する場合はCtrl-Cで停止できます。
上記の手順でセットアップした場合は現在のブランチがmainブランチになっているはずです。以下のコマンドを実行して既存のテストがすべてパスすることも確認しておきましょう。
bundle exec rspec
以下のような表示で終了していればRSpecも正常に動作しています。
Finished in 2.88 seconds (files took 2.39 seconds to load)
70 examples, 0 failures
サーバーが起動しない、テストが正常に動作しない、といった技術的な質問は teratail のようなプログラマ向けQ&Aサイトで質問してください。それでも問題が解決しない場合はGitHubのissuesページで質問していただいて構いません。