Everyday Rails - RSpecによるRailsテスト入門
Everyday Rails - RSpecによるRailsテスト入門
Buy on Leanpub
Everyday Rails - RSpecによるRailsテスト入門

この版のまえがき

改訂版の「Everyday Rails - RSpecによるRailsテスト入門」を手にとっていただき、どうもありがとうございます。改訂版をリリースするまで、長い時間がかかりました。そして、内容も大きく変わりました。本書を読んだみなさんに「長い間待った甲斐があった」と思っていただけると幸いです。

なぜこんなに時間がかかったのでしょうか?前述のとおり、内容は大きく変わりました。本の内容そのものも変わりましたし、Railsにおける一般的なテストの考え方も変わっています。まず後者について説明しましょう。Rails 5.0の登場と同時に、Railsチームはコントローラのテストを事実上非推奨としました。個人的にこれは素晴らしいニュースでした。本書の前の版では説明に 3章 も使っていましたが、私も最初はコントローラのテストを理解するのに非常に苦労したのを思い出しました。そして、最近では以前ほどコントローラのテストを書かなくなりました。

その1年後、Rails 5.1がリリースされ、ついに高レベルのシステムテストが組み込まれました。このレベルのテストは本書を最初に出版したときから採用していたもので、かつてはRailsに自分で組み込む必要がありました。システムテストはRSpecではないので、本書で使っていたものとまったく同じではありません。ですが、Rails標準の構成で開発したいと思う人たちが、アプリケーションを様々なレベルでテストできるようになったのは、とても素晴らしいことだと思います。

一方、RSpecの開発も進んでいます。RSpecにも数多くの新機能が実装され、より表現力豊かにテストを書けるようになりました。私自身を含め、多くの開発者が今なおRSpecを愛用しています。また、RSpecをアプリケーションに組み込むのに、いくつかの手順が必要になる点も変わっていません。

以上が自分ではコントロールできない、外部で起きた変化です。では次に、私が改訂版の「Everyday Rails - RSpecによるRailsテスト入門」に加えた変更をご説明しましょう。きっと以前の版よりも充実した内容になっているはずです。今回の変更点の多くは、RailsやRSpecそれぞれに起きた変化とは関係なく、私自身が「こうしたい」と思って加えた変更です。

本書はもともと「Everyday Rails」というブログに書いていた記事から始まっています。5年前、私はRailsアプリケーションのテストの書き方について、自分が学んだことをブログに書き始めました。ブログ記事は人気を集め、私はそれを新たに書き下ろした内容や、完全なサンプルコードとともに一冊の本にまとめることにしました。その後、本書は私の期待をはるかに超えて多くの人たちに読まれ、私が初心者だった頃と同じようにテストの書き方で困っていた人たちを助けました。

それにしてもソフトウェアというのは面白いもので、その名の通り「ソフト(柔らかい)」です。対象となる問題を大小様々な観点から理解するにつれ、問題を解決するためのアプローチは少しずつ変わってきます。現在私が使っているテストのテクニックは、初期のブログ記事や本書の初版で書いたテクニックと根本的には同じです。しかし、私はこれまでにそのテクニックを増やし、テクニックを厳選し、さらにテクニックを磨き上げてきました。

この1年間で頭を悩ませたのは、どうやってテストにおける「次のレベルの学び方」を本書に落とし込むか、ということでした。つまり、初心者がテストを学び、それから自分自身のテクニックを増やし、厳選し、磨き上げる方法を考えるのに苦労しました。幸いなことに、本書でもともと採用していた学習フレームワークは、今でも正しかったようです。すなわち、最初は簡単なアプリケーションから始めて、それをブラウザでテストします(もしくはAPIであれば、最近はPostmanのようなツールを使うこともあると思います)。それから、小さな単位でテストを書き始めます。最初は明白な仕様をテストします。それからもっと複雑なテストを書きます。今度はその順番をひっくり返します。まずテストを書き、それからコードを書くのです。こうやっていくうちに、効果的なテストの書き方が身に付いていきます。

そうは言うものの、私は前の版で使っていたサンプルアプリケーションに満足できていませんでした。とてもシンプルなアプリケーションなので、新しいテストテクニックを説明していても読者の頭からアプリの仕様が抜け落ちない点は良かったのですが、そのシンプルさゆえに意味のあるテストコードを追加しづらいのが難点でした。また、簡単な修正をコードに加えたいだけなのに、全部の章に渡って同じ修正を加えなければならず、バージョン管理システムで変更の衝突が頻繁に発生していたのも苦労した点の一つです。改訂版のサンプルアプリケーションは大きくなりましたが、それでも大きすぎるレベルではありません。これならずっと快適にテストを書くことができます。ちなみに、これは私が久々にテストファーストで 書かずに 作ったアプリケーションでもあります。

それはさておき、みなさんが本書を楽しみながら読んでくれることを願っています。テストを書いたことがないという方はもちろん、本書の初版から読んでくれている方で、テスト駆動開発に対する私の考え方やその他のテストテクニックがどのように変わってきたのか興味を持っている方も、楽しんで読んでもらえると嬉しいです。公開前に自分で何度も読み直し、問題が無いことを確認したつもりですが、もしかすると読者のみなさんは内容の誤りに気付いたり、別のもっと良い方法を知っていたりするかもしれません。間違いを見つけたり、何か良いアイデアがあったりする場合は、このリリース用の GitHub issues にぜひ報告してください。できるだけ素早く対処します。(訳注: 日本語版のフィードバックはこちらからお願いします。https://leanpub.com/everydayrailsrspec-jp/feedback

改めてみなさんに感謝します。みなさんにこの改訂版を気に入ってもらえることを願っています。そしてGithubやTwitter、Eメールでみなさんの感想が聞けることも楽しみにしています。

日本語版のまえがき

(このまえがきは日本語版の初版リリース時に作成されたものです)

私は好運です。

英語は私の母国語です。そして英語は技術文書の非公式な共通言語になっているようです。私は数多くの本やブログ、スクリーンキャストで勉強し、テスト駆動開発とRSpecを理解することができました。そしてついに、自分でその本を書くこともできました。私には想像することしかできませんが、世界中のソフトウェア開発者の多くは新しいプログラミング言語を学ぶだけでなく、関連する情報源が書かれている外国語もがんばって学ぶ必要があるんですよね。

そして、私はまたもや好運であり、大変嬉しく思っています。なぜかといえば、同じRubyistである伊藤淳一さん、魚振江さん、秋元利春さんが日本語で読みたがっているプログラマのために、 Everyday Rails Testing with RSpec をがんばって翻訳してくれたからです。彼らは本書を新しい読者に届けてくれただけでなく、今後のバージョンの改善に役立つ貴重なフィードバックも返してくれました。

読者のみなさんが淳一さん、振江さん、利春さんの努力の成果を私と同じぐらい楽しんで、感謝することを願っています。そして、あなたの今後のRails開発にも好運が訪れることを願っています!

 

Aaron Sumner

Author

Everyday Rails Testing with RSpec

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 5.1とRSpec 3.6を使用します。これはどちらも執筆時点の現行バージョンです。

本書は次のようなテーマに分けられています。

  • あなたが今読んでいるのが第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 5.1: 最新バージョンのRailsが本書のメインターゲットです。私が知る限り、Rails 3.0以上であれば本書で紹介しているテクニックは適用できるはずです。サンプルコードによっては違いが出るかもしれませんが、差異が出そうな箇所はできる限り伝えていきます。
  • Ruby 2.4: Rails 5.0ではRuby 2.2以上が必須です。もしあなたが古いバージョンのRailsにテストを追加していくのであれば、Ruby 2.0や2.1、2.2でも大丈夫だと思います。
  • RSpec 3.6: RSpec 3.0は2014年の春にリリースされました。RSpec 3.6はRails 5.1とほぼ同時にリリースされました。RSpec 3.6は3.0とほぼ互換性を保っています。RSpec 3.6はRSpec 2.14に近いシンタックスになっていますが、多少異なる点もあります。

本書で使用しているバージョン固有の用法等があれば、できる限り伝えていきます。もし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/everydayrails/everydayrails-rspec-2017/issues(訳注: 日本語版に関するフィードバックは https://leanpub.com/everydayrailsrspec-jp/feedback から投稿してください。 )

gemのバージョンに関する注意点

本書と本書のサンプルアプリケーションで使用しているgemのバージョンは、このRSpec 3.6/Rails 5.1版を執筆していたとき(2017年春頃)の現行バージョンです。当然、どのgemも頻繁にアップデートされますので、Rubygems.orgやGitHub、またはお気に入りのRuby新着情報フィードでアップデート情報をチェックしてください。

サンプルアプリケーションについて

本書で使用するサンプルアプリケーションはプロジェクト管理アプリです。TrelloやBasecampほど多機能でカッコいいものではありませんが、テストを書き始めるためには十分な機能を備えています。

はじめに、このアプリケーションは次のような機能を持っています。

  • ユーザーはプロジェクトを追加できる。追加したプロジェクトはそのユーザーにだけ見える。
  • ユーザーはタスクとメモと添付ファイルをプロジェクトに追加できる。
  • ユーザーはタスクを完了済みにできる。
  • ユーザーアカウントはGravatarによって提供されるアバターを持っている。
  • 開発者はパブリックAPIを使って、外部のクライアントアプリケーションを開発できる。

私はここまで意図的にRailsのデフォルトのジェネレータだけを使ってアプリケーション全体を作成しました( 01_untested ブランチにあるサンプルコードを参照)。つまりこれは test ディレクトリに何も変更していないテストファイルとフィクスチャがそのまま格納されているということです。この時点で bin/rails test を実行すると、いくつかのテストはそのままでパスするかもしれません。しかし、本書はRSpecの本ですので、testフォルダは用無しになります。RSpecが使えるようにRailsをセットアップし、信頼できるテストスイートを構築していきましょう。これが今から私たちが順を追って見ていく内容です。

まず最初にすべきことは、RSpecを使うようにアプリケーションの設定を変更することです。では始めましょう!

3. モデルスペック

RSpecのインストールが完了し、これで信頼性の高いテストスイートを構築する準備が整いました。まずアプリケーションのコアとなる部分、すなわちモデルから始めてみましょう。

本章では次のようなタスクを完了させます。

  • まず既存のモデルに対してモデルスペックを作ります。
  • それからモデルのバリデーション、クラスメソッド、インスタンスメソッドのテストを書きます。テストを作りながらスペックの整理もします。

既存のモデルがあるので、最初のスペックファイルは手作業で追加します。それから新しいモデルをアプリケーションに追加します。こうすると第2章で設定した便利なRSpecのジェネレータが仮のファイルを作成してくれます。

モデルスペックの構造

私はモデルレベルのテストが一番学習しやすいと思います。なぜならモデルをテストすればアプリケーションのコアとなる部分をテストすることになるからです。このレベルのコードが十分にテストされていれば土台が堅牢になり、そこから信頼性の高いコードベースを構築できます。

はじめに、モデルスペックには次のようなテストを含めましょう。

  • 有効な属性で初期化された場合は、モデルの状態が有効(valid)になっていること
  • バリデーションを失敗させるデータであれば、モデルの状態が有効になっていないこと
  • クラスメソッドとインスタンスメソッドが期待通りに動作すること

良い機会なので、ここでモデルスペックの基本構成を見てみましょう。スペックの記述をアウトラインと考えるのが便利です。たとえば、メインとなるUserモデルの要件を見てみましょう。

describe User do
  # 姓、名、メール、パスワードがあれば有効な状態であること
  it "is valid with a first name, last name, email, and password"
  # 名がなければ無効な状態であること
  it "is invalid without a first name"
  # 姓がなければ無効な状態であること
  it "is invalid without a last name"
  # メールアドレスがなければ無効な状態であること
  it "is invalid without an email address"
  # 重複したメールアドレスなら無効な状態であること
  it "is invalid with a duplicate email address"
  # ユーザーのフルネームを文字列として返すこと
  it "returns a user's full name as a string"
end

このアウトラインはすぐあとに展開していきますが、初心者はここからたくさんのことが学べます。これは本当にシンプルなモデルのシンプルなスペックです。しかし、次のような4つのベストプラクティスを示しています。

  • 期待する結果をまとめて記述(describe)している。 このケースではUserモデルがどんなモデルなのか、そしてどんな振る舞いをするのかということを説明しています。
  • example( it で始まる1行)一つにつき、結果を一つだけ期待している。 私が first_namelast_nameemail のバリデーションをそれぞれ分けてテストしている点に注意してください。こうすれば、exampleが失敗したときに問題が起きたバリデーションを 特定 できます。原因調査のためにRSpecの出力結果を調べる必要はありません。少なくともそこまで細かく調べずに済むはずです。
  • どのexampleも明示的である。 技術的なことを言うと、 it のあとに続く説明用の文字列は必須ではありません。しかし、省略してしまうとスペックが読みにくくなります。
  • 各exampleの説明は動詞で始まっている。shouldではない。 期待する結果を声に出して読んでみましょう。 User is invalid without a first name (名がなければユーザーは無効な状態である)、 User is invalid without a last name (姓がなければユーザーは無効な状態である)、 User returns a user’s full name as a string (ユーザーは文字列としてユーザーのフルネームを返す)。可読性は非常に重要であり、RSpecのキーとなる機能です!

こうしたベストプラクティスを念頭に置きながら User モデルのスペックを書いてみましょう。

モデルスペックを作成する

第2章ではモデルやコントローラを追加するたびに定型のテストファイルが自動的に作成されるようにRSpecをセットアップしました。ジェネレータはいつでも起動できます。最初のモデルスペックを作成するため、この作業の出発地点となるファイルを実際に生成してみましょう。

まず、 rspec:model ジェネレータをコマンドラインから実行してください。

$ bin/rails g rspec:model user

RSpecは新しいファイルを作成したことを報告します。

Running via Spring preloader in process 32008
      create  spec/models/user_spec.rb

作成されたファイルを開き、内容を確認しましょう。

spec/models/user_spec.rb
1 require 'rails_helper'
2 
3 RSpec.describe User, type: :model do
4   pending "add some examples to (or delete) #{__FILE__}"
5 end

この新しいファイルを見れば、RSpecの構文と規約がわかります。まず、このファイルでは rails_helperrequire しています。この記述はテストスイート内のほぼすべてのファイルで必要になります。この記述でRSpecに対し、ファイル内のテストを実行するためにRailsアプリケーションの読み込みが必要であることを伝えています。次に、 describe メソッドを使って、 User という名前の モデル のテストをここに書くことを示しています。テスト駆動開発の練習を始める第11章になったら pending 機能のことを詳しく説明しますが、とりあえずここでは bin/rspec を使ってこのテストを実行してみましょう。いったい何が起こるでしょうか?

Running via Spring preloader in process 41653

User
  add some examples to (or delete)
  /Users/asumner/code/examples/projects/spec/models/user_spec.rb
  (PENDING: Not yet implemented)

Pending: (Failures listed here are expected and do not affect your
suite's status)

  1) User add some examples to (or delete)
  /Users/asumner/code/examples/projects/spec/models/user_spec.rb
     # Not yet implemented
     # ./spec/models/user_spec.rb:4


Finished in 0.00107 seconds (files took 0.43352 seconds to load)
1 example, 0 failures, 1 pending

describe の外枠はそのままにして、その内側を先ほど作成したアウトラインに置き換えてみましょう。

spec/models/user_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe User, type: :model do
 4   # 姓、名、メール、パスワードがあれば有効な状態であること
 5   it "is valid with a first name, last name, email, and password"
 6   # 名がなければ無効な状態であること
 7   it "is invalid without a first name"
 8   # 姓がなければ無効な状態であること
 9   it "is invalid without a last name"
10   # メールアドレスがなければ無効な状態であること
11   it "is invalid without an email address"
12   # 重複したメールアドレスなら無効な状態であること
13   it "is invalid with a duplicate email address"
14   # ユーザーのフルネームを文字列として返すこと
15   it "returns a user's full name as a string"
16 end

詳細はこのあと追加していきますが、この状態でコマンドラインからスペックを実行すると(コマンドラインから bin/rspec とタイプしてください)、出力結果は次のようになります。

Running via Spring preloader in process 32556

User
  is valid with a first name, last name, email, and password (PENDING:
  Not yet implemented)
  is invalid without a first name (PENDING: Not yet implemented)
  is invalid without a last name (PENDING: Not yet implemented)
  is invalid without an email address (PENDING: Not yet implemented)
  is invalid with a duplicate email address (PENDING: Not yet implemented)
  returns a user's full name as a string (PENDING: Not yet implemented)

Pending: (Failures listed here are expected and do not affect your
suite's status)

  1) User is valid with a first name, last name, email, and password
     # Not yet implemented
     # ./spec/models/user_spec.rb:5

  2) User is invalid without a first name
     # Not yet implemented
     # ./spec/models/user_spec.rb:7

  3) User is invalid without a last name
     # Not yet implemented
     # ./spec/models/user_spec.rb:9

  4) User is invalid without an email address
     # Not yet implemented
     # ./spec/models/user_spec.rb:11

  5) User is invalid with a duplicate email address
     # Not yet implemented
     # ./spec/models/user_spec.rb:13

  6) User returns a user's full name as a string
     # Not yet implemented
     # ./spec/models/user_spec.rb:15


Finished in 0.00176 seconds (files took 2.18 seconds to load)
6 examples, 0 failures, 6 pending

すばらしい!6つの保留中(pending)のスペックができあがりました。私たちはまだ実行可能なテストを何も書いていないので、RSpecはここで作成したスペックを pending と表示しています。それでは実際にテストを書いていきましょう。まずは一番最初のexampleから始めます。

RSpecの構文

2012年にRSpecの開発チームはそれまで使われてきた should に代わる新しい構文をバージョン2.11に追加しました。そうです、これは本書の完成版を最初にリリースしたわずか数日後の出来事でした。こうした変更に追い付いていくのは大変です!

この新しいアプローチを使うと古い should 構文で発生していた、いくつかの技術的な問題が起こりにくくなります。これからは「~が期待した結果と一致すべきだ/すべきでない(something should or should_not match expected output)」と言う代わりに、「私は~が~になる/ならないことを期待する(I expect something to or not_to be something else)」と言いましょう。

例として、このテスト、つまりエクスペクテーション(expectation、期待する内容)を見てみましょう。このexampleの場合、2 + 1はいつでも3に等しいはずですよね?古いRSpecの構文ではこのように書きます。

# 2と1を足すと3になること
it "adds 2 and 1 to make 3" do
  (2 + 1).should eq 3
end

新しい構文ではテストする値を expect() メソッドに渡し、それに続けてマッチャを呼び出します。

# 2と1を足すと3になること
it "adds 2 and 1 to make 3" do
  expect(2 + 1).to eq 3
end

GoogleやStack OverflowでRSpecに関する質問を検索したり、古いRailsアプリケーションを開発したりすると、今でも古い should 構文を使ったコードをよく見かけると思います。この構文は現行バージョンのRSpecでも動作しますが、使うと非推奨であるとの警告が出力されます。設定を変更すればこの警告を出力しないようにすることも できます が、そんなことはせずに新しい expect() 構文を学習した方が良いと思います。

では、実際のexampleではどうなるでしょうか?Userモデルの最初のエクスペクテーションで使ってみましょう。

spec/models/user_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe User, type: :model do
 4   # 姓、名、メール、パスワードがあれば有効な状態であること
 5   it "is valid with a first name, last name, email, and password" do
 6     user = User.new(
 7       first_name: "Aaron",
 8       last_name:  "Sumner",
 9       email:      "tester@example.com",
10       password:   "dottle-nouveau-pavilion-tights-furze",
11     )
12     expect(user).to be_valid
13   end
14 
15   # 名がなければ無効な状態であること
16   it "is invalid without a first name"
17   # 姓がなければ無効な状態であること
18   it "is invalid without a last name"
19   # メールアドレスがなければ無効な状態であること
20   it "is invalid without an email address"
21   # 重複したメールアドレスなら無効な状態であること
22   it "is invalid with a duplicate email address"
23   # ユーザーのフルネームを文字列として返すこと
24   it "returns a user's full name as a string"
25 end

この単純なexampleは be_valid というRSpecのマッチャを使って、モデルが有効な状態を理解できているかどうかを検証しています。まずオブジェクトを作成し(このケースでは新しく作られているが保存はされていない User クラスのインスタンスを作成し、 user という名前の変数に格納しています)、それからオブジェクトを expect に渡して、マッチャと比較しています。

それでは bin/rspec をコマンドラインから再実行してみましょう。すると、1つのexampleがパスしたと表示されるはずです。

Running via Spring preloader in process 32678

User
  is valid with a first name, last name and email, and password
  is invalid without a first name (PENDING: Not yet implemented)
  is invalid without a last name (PENDING: Not yet implemented)
  is invalid without an email address (PENDING: Not yet implemented)
  is invalid with a duplicate email address (PENDING: Not yet implemented)
  returns a user's full name as a string (PENDING: Not yet implemented)

Pending: (Failures listed here are expected and do not affect your
suite's status)

  1) User is invalid without a first name
     # Not yet implemented
     # ./spec/models/user_spec.rb:16

  2) User is invalid without a last name
     # Not yet implemented
     # ./spec/models/user_spec.rb:18

  3) User is invalid without an email address
     # Not yet implemented
     # ./spec/models/user_spec.rb:20

  4) User is invalid with a duplicate email address
     # Not yet implemented
     # ./spec/models/user_spec.rb:22

  5) User returns a user's full name as a string
     # Not yet implemented
     # ./spec/models/user_spec.rb:24


Finished in 0.02839 seconds (files took 0.28886 seconds to load)
6 examples, 0 failures, 5 pending

おめでとうございます。これで最初のテストが完成しました!ではこれからもっとコードをテストしていって、保留中のテストを完全になくしてしまいましょう。

バリデーションをテストする

バリデーションはテストの自動化に慣れるための良い題材です。バリデーションのテストはたいてい1~2行で書けます。では first_name バリデーションのスペックについて詳細を見てみましょう。

spec/models/user_spec.rb
1 # 名がなければ無効な状態であること
2 it "is invalid without a first name" do
3   user = User.new(first_name: nil)
4   user.valid?
5   expect(user.errors[:first_name]).to include("can't be blank")
6 end

今回は新しく作ったユーザー( first_name には明示的に nil をセットします)に対して valid? メソッドを呼び出すと有効(valid)に ならず 、ユーザーの first_name 属性にエラーメッセージが付いていることを 期待(expect) します。RSpecをもう一度実行すると、二番目までのスペックがパスするはずです。ここでRSpecの include マッチャについて確認しましょう。このマッチャは繰り返し可能な値(enumerable value)の中に、ある値が存在するかどうかをチェックします。ではRSpecをもう一度実行します。すると、今回は2つのスペックがパスするはずです。

ここまでのアプローチにはちょっとした問題があります。現時点で2つのテストがパスしていますが、私たちはまだテストが 失敗 するところを見ていません。これは警告すべき兆候です。特に、テストを書き始めたタイミングであればなおさらです。私たちはテストコードが意図した通りに動いていることを確認しなければなりません。これは「テスト対象のコードでいろいろ試すアプローチ(excercising the code under test)」としても知られています。

誤判定ではないことを証明するためには二つのやり方があります。ひとつめは、 toto_not に変えてエクスペクテーションを反転させてみます。

spec/models/user_spec.rb
1 # 名がなければ無効な状態であること
2 it "is invalid without a first name" do
3   user = User.new(first_name: nil)
4   user.valid?
5   expect(user.errors[:first_name]).to_not include("can't be blank")
6 end

当然のごとく、RSpecはテストの失敗を報告します。

Failures:

  1) User is invalid without a first name
     Failure/Error: expect(user.errors[:first_name]).to_not
     include("can't be blank")
       expected ["can't be blank"] not to include "can't be blank"
     # ./spec/models/user_spec.rb:17:in `block (2 levels) in <top \
      (required)>'
     # /Users/asumner/.rvm/gems/ruby-2.4.1/gems/spring-commands-rspec-\
      1.0.4/lib/spring/commands/rspec.rb:18:in `call'
     # -e:1:in `<main>'

Finished in 0.06211 seconds (files took 0.28541 seconds to load)
6 examples, 1 failure, 5 pending

Failed examples:

rspec ./spec/models/user_spec.rb:14 # User is invalid without a first name

もうひとつ、アプリケーション側のコードを変更して、テストの実行結果にどんな変化が起きるか確認する方法もあります。先ほどのテストコードの変更を元に戻し( to_notto に戻す)、それから User モデルを開いて first_name のバリデーションをコメントアウトしてください。

app/models/user.rb
 1 class User < ApplicationRecord
 2   # Include default devise modules. Others available are:
 3   # :confirmable, :lockable, :timeoutable and :omniauthable
 4   devise :database_authenticatable, :registerable,
 5          :recoverable, :rememberable, :trackable, :validatable
 6 
 7   # validates :first_name, presence: true
 8   validates :last_name, presence: true
 9 
10   # 残りのコードは省略 ...

スペックを再実行すると、再度失敗が表示されるはずです。これはすなわち、私たちはRSpecに対して名を持たないユーザーは無効であると伝えたのに、アプリケーション側がその仕様を実装していないことを意味しています。

この二つの方法は、自分の書いたテストが期待どおりに動いているかどうか確認する簡単な方法です。シンプルなバリデーションからもっと複雑なロジックに進むときであれば、特に有効です。また、この方法は既存のアプリケーションをテストするためにも有効です。もしテストの出力結果に何も変化がなければ、それはよいチャンスです。変化がない場合は、テストがアプリケーション側のコードと連携していなかったり、コードが期待した動きと異なっていたりすることを意味しています。

では、 :last_name のバリデーションも同じアプローチでテストしてみましょう。

spec/models/user_spec.rb
1 # 姓がなければ無効な状態であること
2 it "is invalid without a last name" do
3   user = User.new(last_name: nil)
4   user.valid?
5   expect(user.errors[:last_name]).to include("can't be blank")
6 end

「こんなテストは役に立たない。モデルに含まれるすべてのバリデーションを確認しようとしたらどれくらい大変になるのかわかっているのか?」そんなふうに思っている人もいるかもしれません。ですが、実際はあなたが考えている以上にバリデーションは書き忘れやすいものです。しかし、それよりもっと大事なことは、テストを書いている 最中に モデルが持つべきバリデーションについて考えれば、バリデーションの追加を忘れにくくなるということです。(このプロセスはテスト駆動開発でコードを書くのが理想的ですし、最後は実際そうします。)

ここまでに得た知識を使って、もう少し複雑なテストを書いてみましょう。今回はemail属性のユニークバリデーションをテストします。

spec/models/user_spec.rb
 1 # 重複したメールアドレスなら無効な状態であること
 2 it "is invalid with a duplicate email address" do
 3   User.create(
 4     first_name:  "Joe",
 5     last_name:  "Tester",
 6     email:      "tester@example.com",
 7     password:   "dottle-nouveau-pavilion-tights-furze",
 8   )
 9   user = User.new(
10     first_name:  "Jane",
11     last_name:  "Tester",
12     email:      "tester@example.com",
13     password:   "dottle-nouveau-pavilion-tights-furze",
14   )
15   user.valid?
16   expect(user.errors[:email]).to include("has already been taken")
17 end

ここではちょっとした違いがあることに注意してください。このケースではテストの前にユーザーを保存しました( User に対して new の代わりに create を呼んでいます)。それから2件目のユーザーをテスト対象のオブジェクトとしてインスタンス化しました。もちろん、最初に保存されたユーザーは有効な状態(姓、名、メール、パスワードが全部ある)であり、なおかつ、同一のメールアドレスも設定されている必要があります。第4章ではこのプロセスをもっと効率よく処理する方法を説明します。では、 bin/rspec を実行して新しいテストの出力結果を確認してください。

続いてもっと複雑なバリデーションをテストしましょう。Userモデルの話はいったん横に置いて、今度はProjectモデルに着目します。たとえば、ユーザーは同じ名前のプロジェクトを作成できないという要件があったとします。つまり、プロジェクト名はユーザーごとにユニークでなければならない、ということです。別の言い方をすると、私は Paint the house (家を塗る)という複数のプロジェクトを持つことはできないが、あなたと私はそれぞれ Paint the house というプロジェクトを持つことができる、ということです。あなたならどうやってテストしますか?

ではProjectモデル用に新しいスペックファイルを作成しましょう。

$ bin/rails g rspec:model project

続いて、作成されたファイルに二つのexampleを追加します。ここでテストしたいのは、一人のユーザーは同じ名前で二つのプロジェクトを作成できないが、ユーザーが異なるときは同じ名前のプロジェクトを作成できる、という要件です。

spec/models/project_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe Project, type: :model do
 4   # ユーザー単位では重複したプロジェクト名を許可しないこと
 5   it "does not allow duplicate project names per user" do
 6     user = User.create(
 7       first_name: "Joe",
 8       last_name:  "Tester",
 9       email:      "joetester@example.com",
10       password:   "dottle-nouveau-pavilion-tights-furze",
11     )
12 
13     user.projects.create(
14       name: "Test Project",
15     )
16 
17     new_project = user.projects.build(
18       name: "Test Project",
19     )
20 
21     new_project.valid?
22     expect(new_project.errors[:name]).to include("has already been taken")
23   end
24 
25   # 二人のユーザーが同じ名前を使うことは許可すること
26   it "allows two users to share a project name" do
27     user = User.create(
28       first_name: "Joe",
29       last_name:  "Tester",
30       email:      "joetester@example.com",
31       password:   "dottle-nouveau-pavilion-tights-furze",
32     )
33 
34     user.projects.create(
35       name: "Test Project",
36     )
37 
38     other_user = User.create(
39       first_name: "Jane",
40       last_name:  "Tester",
41       email:      "janetester@example.com",
42       password:   "dottle-nouveau-pavilion-tights-furze",
43     )
44 
45     other_project = other_user.projects.build(
46       name: "Test Project",
47     )
48 
49     expect(other_project).to be_valid
50   end
51 end

今回は User モデルと Project モデルがActive Recordのリレーションで互いに関連するため、そのぶん多くの情報を記述する必要があります。最初のexampleでは両方のプロジェクトを割り当てられた一人のユーザーがいます。二つ目のexampleでは二つの別々のプロジェクトに同じ名前が割り当てられ、それらが別々のユーザーに属しています。ここでは以下の点に注意してください。二つのexampleはどちらもユーザーを create してデータベースに保存しています。これはユーザーをテスト対象のプロジェクトに割り当てる必要があるためです。

Project モデルには以下のようなバリデーションが設定されています。

app/models/project.rb
validates :name, presence: true, uniqueness: { scope: :user_id }

今回作成したスペックは問題なくパスします。ですが、例のチェックをお忘れなく。一時的にバリデーションをコメントアウトしたり、テストを書き換えたりして、結果が変わることを確認してください。テストはちゃんと失敗するでしょうか?

もちろん、バリデーションはscopeが一つしかないような単純なものばかりではなく、もっと複雑になる場合があります。もしかするとあなたは複雑な正規表現やカスタムバリデータを使っているかもしれません。こうしたバリデーションもテストする習慣を付けてください。正常系のパターンだけでなく、エラーが発生する条件もテストしましょう。たとえば、これまでに作ってきたexampleではオブジェクトが nil で初期化された場合の実行結果もテストしました。もし数値しか受け付けない属性のバリデーションがあるなら、文字列を渡してください。もし4文字から8文字の文字列を要求するバリデーションがあるなら、3文字と9文字の文字列を渡してください。

インスタンスメソッドをテストする

それではUserモデルのテストに戻ります。このサンプルアプリケーションでは、ユーザーの姓と名を毎回連結して新しい文字列を作るより、 @user.name を呼び出すだけでフルネームが出力されるようにした方が便利です。というわけでこんなメソッドが User クラスに作ってあります。

app/models/user.rb
def name
  [firstname, lastname].join(' ')
end

バリデーションのexampleと同じ基本的なテクニックでこの機能のexampleを作ることができます。

spec/models/user_spec.rb
1 it "returns a user's full name as a string" do
2   user = User.new(
3     first_name: "John",
4     last_name:  "Doe",
5     email:      "johndoe@example.com",
6   )
7   expect(user.name).to eq "John Doe"
8 end

テストデータを作り、それからあなたが期待する振る舞いをRSpecに教えてあげてください。簡単ですね。では続けましょう。

クラスメソッドとスコープをテストする

このアプリケーションには渡された文字列でメモ(note)を検索する機能を用意してあります。念のため説明しておくと、この機能はNoteモデルにスコープとして実装されています。

app/models/note.rb
1 scope :search, ->(term) {
2   where("LOWER(message) LIKE ?", "%#{term.downcase}%")
3 }

ではNoteモデル用に3つめのファイルをテストスイートに追加しましょう。 rspec:model ジェネレータでファイルを作ったら、最初のテストを追加してください。

spec/models/note_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe Note, type: :model do
 4   # 検索文字列に一致するメモを返すこと
 5   it "returns notes that match the search term" do
 6     user = User.create(
 7       first_name: "Joe",
 8       last_name:  "Tester",
 9       email:      "joetester@example.com",
10       password:   "dottle-nouveau-pavilion-tights-furze",
11     )
12 
13     project = user.projects.create(
14       name: "Test Project",
15     )
16 
17     note1 = project.notes.create(
18       message: "This is the first note.",
19       user: user,
20     )
21     note2 = project.notes.create(
22       message: "This is the second note.",
23       user: user,
24     )
25     note3 = project.notes.create(
26       message: "First, preheat the oven.",
27       user: user,
28     )
29 
30     expect(Note.search("first")).to include(note1, note3)
31     expect(Note.search("first")).to_not include(note2)
32   end
33 end

search スコープは検索文字列に一致するメモのコレクションを返します。返されたコレクションは一致したメモだけが含まれるはずです。その文字列を含まないメモはコレクションに含まれません。

このテストでは次のような実験ができます。 toto_not に変えたらどうなるでしょうか?もしくは検索文字列を含むメモをさらに追加したらどうなるでしょうか?

失敗をテストする

正常系のテストは終わりました。ユーザーが文字列検索すると結果が返ってきます。しかし、結果が返ってこない文字列で検索したときはどうでしょうか?そんな場合もテストした方が良いです。次のスペックがそのテストになります。

spec/models/note_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe Note, type: :model do
 4   # 検索結果を検証するスペック...
 5 
 6   # 検索結果が1件も見つからなければ空のコレクションを返すこと
 7   it "returns an empty collection when no results are found" do
 8     user = User.create(
 9       first_name: "Joe",
10       last_name:  "Tester",
11       email:      "joetester@example.com",
12       password:   "dottle-nouveau-pavilion-tights-furze",
13     )
14 
15     project = user.projects.create(
16       name: "Test Project",
17     )
18 
19     note1 = project.notes.create(
20       message: "This is the first note.",
21       user: user,
22     )
23     note2 = project.notes.create(
24       message: "This is the second note.",
25       user: user,
26     )
27     note3 = project.notes.create(
28       message: "First, preheat the oven.",
29       user: user,
30     )
31 
32     expect(Note.search("message")).to be_empty
33   end
34 end

このスペックではRSpecの be_empty マッチャを使って、 Note.search("message") を実行して返却された配列に何も含まれていないことを検証しています。実際、実行結果は空になるので、テストはパスします!これで理想的な結果、すなわち結果が返ってくる文字列で検索した場合だけでなく、結果が返ってこない文字列で検索した場合もテストしたことになります。

マッチャについてもっと詳しく

これまで四つのマッチャ( be_valideqincludebe_empty )を実際に使いながら見てきました。最初に使ったのは be_valid です。このマッチャは rspec-rails gemが提供するマッチャで、Railsのモデルの有効性をテストします。 eqincluderspec-expectations で定義されているマッチャで、前章でRSpecをセットアップしたときに rspec-rails と一緒にインストールされました。

RSpecが提供するデフォルトのマッチャをすべて見たい場合はGitHubにあるrspec-expectationsリポジトリREADME が参考になるかもしれません。この中に出てくるマッチャのいくつかは本書全体を通して説明していきます。また、第8章では自分でカスタムマッチャを作る方法も説明します。

describe、context、before、afterを使ってスペックをDRYにする

ここまでに作成したメモ用のスペックには冗長なコードが含まれます。具体的には、各exampleの中ではまったく同じ4つのオブジェクトを作成しています。アプリケーションコードと同様に、DRY原則はテストコードにも当てはまります(いくつか例外もあるので、のちほど説明します)。ではRSpecの機能をさらに活用してテストコードをきれいにしてみましょう。

先ほど作った Note モデルのスペックに注目してみましょう。まず最初にやるべきことは describe ブロックを describe Note ブロックの 中に 作成することです。これは検索機能にフォーカスするためです。アウトラインを抜き出すと、このようになります。

spec/models/note_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe Note, type: :model do
 4 
 5   # バリデーション用のスペックが並ぶ
 6 
 7   # 文字列に一致するメッセージを検索する
 8   describe "search message for a term" do
 9     # 検索用のexampleが並ぶ ...
10   end
11 end

二つの context ブロックを加えてさらにexampleを切り分けましょう。一つは「一致するデータが見つかるとき」で、もう一つは「一致するデータが1件も見つからないとき」です。

spec/models/note_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe Note, type: :model do
 4 
 5   # 他のスペックが並ぶ
 6 
 7   # 文字列に一致するメッセージを検索する
 8   describe "search message for a term" do
 9 
10     # 一致するデータが見つかるとき
11     context "when a match is found" do
12       # 一致する場合のexampleが並ぶ ...
13     end
14 
15     # 一致するデータが1件も見つからないとき
16     context "when no match is found" do
17       # 一致しない場合のexampleが並ぶ ...
18     end
19   end
20 end

お気づきかもしれませんが、このようにexampleのアウトラインを作ると、同じようなexampleをひとまとめにして分類できます。こうするとスペックがさらに読みやすくなります。では最後に、 before フックを利用してスペックのリファクタリングを完了させましょう。 before ブロックの中に書かれたコードは内側の各テストが実行される前に実行されます。また、 before ブロックは describecontext ブロックによってスコープが限定されます。たとえばこの例で言うと、 before ブロックのコードは "search message for a term" ブロックの内側にある全部のテストに先立って実行されます。ですが、新しく作ったdescribeブロックの外側にあるその他のexampleの前には実行されません。

spec/models/note_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe Note, type: :model do
 4 
 5   before do
 6     # このファイルの全テストで使用するテストデータをセットアップする
 7   end
 8 
 9   # バリデーションのテストが並ぶ
10 
11   # 文字列に一致するメッセージを検索する
12   describe "search message for a term" do
13 
14     before do
15       # 検索機能の全テストに関連する追加のテストデータをセットアップする
16     end
17 
18     # 一致するデータが見つかるとき
19     context "when a match is found" do
20       # 一致する場合のexampleが並ぶ ...
21     end
22 
23     # 一致するデータが1件も見つからないとき
24     context "when no match is found" do
25       # 一致しない場合のexampleが並ぶ ...
26     end
27   end
28 end

RSpecの before フックはスペック内の冗長なコードを認識し、きれいにするための良い出発点になります。これ以外にも冗長なテストコードをきれいにするテクニックはありますが、 before を使うのが最も一般的かもしれません。 before ブロックはexampleごとに、またはブロック内の各exampleごとに、またはテストスイート全体を実行するごとに実行されます。

  • before(:each)describe または context ブロック内の 各(each) テストの前に実行されます。好みに応じて before(:example) というエイリアスを使ってもいいですし、上のサンプルコードで書いたように before だけでも構いません。もしブロック内に4つのテストがあれば、 before のコードも4回実行されます。
  • before(:all)describe または context ブロック内の 全(all) テストの前に一回だけ実行されます。かわりに before(:context) というエイリアスを使っても構いません。こちらは before のコードは一回だけ実行され、それから4つのテストが実行されます。
  • before(:suite) はテストスイート全体の全ファイルを実行する前に実行されます。

before(:all)before(:suite) は時間のかかる独立したセットアップ処理を1回だけ実行し、テスト全体の実行時間を短くするのに役立ちます。ですが、この機能を使うとテスト全体を汚染してしまう原因にもなりかねません。可能な限り before(:each) を使うようにしてください。

もしexampleの実行後に後片付けが必要になるのであれば(たとえば外部サービスとの接続を切断する場合など)、 after フックを使って各exampleのあと(after)に後片付けすることもできます。 before と同様、 after にも eachallsuite のオプションがあります。RSpecの場合、デフォルトでデータベースの後片付けをやってくれるので、私は after を使うことはほとんどありません。

さて、整理後の全スペックを見てみましょう。

spec/models/note_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe Note, type: :model do
 4   before do
 5     @user = User.create(
 6       first_name: "Joe",
 7       last_name:  "Tester",
 8       email:      "joetester@example.com",
 9       password:   "dottle-nouveau-pavilion-tights-furze",
10     )
11 
12     @project = @user.projects.create(
13       name: "Test Project",
14     )
15   end
16 
17   # ユーザー、プロジェクト、メッセージがあれば有効な状態であること
18   it "is valid with a user, project, and message" do
19     note = Note.new(
20       message: "This is a sample note.",
21       user: @user,
22       project: @project,
23     )
24     expect(note).to be_valid
25   end
26 
27   # メッセージがなければ無効な状態であること
28   it "is invalid without a message" do
29     note = Note.new(message: nil)
30     note.valid?
31     expect(note.errors[:message]).to include("can't be blank")
32   end
33 
34   # 文字列に一致するメッセージを検索する
35   describe "search message for a term" do
36     before do
37       @note1 = @project.notes.create(
38         message: "This is the first note.",
39         user: @user,
40       )
41       @note2 = @project.notes.create(
42         message: "This is the second note.",
43         user: @user,
44       )
45       @note3 = @project.notes.create(
46         message: "First, preheat the oven.",
47         user: @user,
48       )
49     end
50 
51     # 一致するデータが見つかるとき
52     context "when a match is found" do
53       # 検索文字列に一致するメモを返すこと
54       it "returns notes that match the search term" do
55         expect(Note.search("first")).to include(@note1, @note3)
56       end
57     end
58 
59     # 一致するデータが1件も見つからないとき
60     context "when no match is found" do
61       # 空のコレクションを返すこと
62       it "returns an empty collection" do
63         expect(Note.search("message")).to be_empty
64       end
65     end
66   end
67 end

みなさんはもしかするとテストデータのセットアップ方法が少し変わったことに気づいたかもしれません。セットアップの処理を各テストから before ブロックに移動したので、各ユーザーはインスタンス変数にアサインする必要があります。そうしないとテストの中で変数名を指定してデータにアクセスできないからです。

これらのスペックを実行すると、こんなふうに素敵なアウトラインが表示されます(第2章でドキュメント形式を使うようにRSpecを設定したからです)。

Note
  is valid with a user, project, and message
  is invalid without a message
  search message for a term
    when a match is found
      returns notes that match the search term
    when no match is found
      returns an empty collection

Project
  does not allow duplicate project names per user
  allows two users to share a project name

User
  is valid with a first name, last name and email, and password
  is invalid without a first name
  is invalid without a last name
  is invalid with a duplicate email address
  returns a user's full name as a string

Finished in 0.22564 seconds (files took 0.32225 seconds to load)
11 examples, 0 failures
どれくらいDRYだとDRYすぎるのか?

本章では長い時間をかけてスペックを理解しやすいブロックに分けて整理しました。しかし、これは弊害を起こしやすい機能です。

exampleのテスト条件をセットアップする際、可読性を考えてDRY原則に違反するのは問題ありません。私はそう考えています。もし自分がテストしている内容を確認するために、大きなスペックファイルを頻繁にスクロールしているようなら(もしくはあとで説明する外部のサポートファイルを大量に読み込んでいるようなら)、テストデータのセットアップを小さな describe ブロックの中で重複させることを検討してください。 describe ブロックの中だけでなく、exampleの中でもOKです。

とはいえ、そんな場合でも変数とメソッドに良い名前を付けるのは大変効果的です。たとえば上のスペックでは @note1@note2 のような名前をテスト用のメモに使いました。しかし、場合によっては @matching_note (一致するメモ)や @note_with_numbers_only (数字だけのメモ)といった変数名を使いたくなるかもしれません。何が適切かはテストする内容に依りますが、一般論としてはわかりやすい変数名とメソッド名を付けるように心がけてください!

このトピックについては第8章でさらに詳しく説明します。

まとめ

本章ではモデルのテストにフォーカスしましたが、このあとに登場するモデル以外のスペックでも使えるその他の重要なテクニックもたくさん説明しました。

  • 期待する結果は能動形で明示的に記述すること。 exampleの結果がどうなるかを動詞を使って説明してください。チェックする結果はexample一つに付き一個だけにしてください。
  • 起きて ほしい ことと、起きて ほしくない ことをテストすること。 exampleを書くときは両方のパスを考え、その考えに沿ってテストを書いてください。
  • 境界値テストをすること。 もしパスワードのバリデーションが4文字以上10文字以下なら、8文字のパスワードをテストしただけで満足しないでください。4文字と10文字、そして3文字と11文字もテストするのが良いテストケースです。(もちろん、なぜそんなに短いパスワードを許容し、なぜそれ以上長いパスワードを許容しないのか、と自問するチャンスかもしれません。テストはアプリケーションの要件とコードを熟考するための良い機会でもあります。)
  • 可読性を上げるためにスペックを整理すること。 describecontext はよく似たexampleを分類してアウトライン化します。 before ブロックと after ブロックは重複を取り除きます。しかし、テストの場合はDRYであることよりも読みやすいことの方が重要です。もし頻繁にスペックファイルをスクロールしていることに気付いたら、それはちょっとぐらいリピートしても問題ないというサインです。

アプリケーションに堅牢なモデルスペックを揃えたので、あなたは順調にコードの信頼性を上げてきています。

Q&A

describeとcontextはどう使い分けるべきでしょうか? RSpecの立場からすれば、あなたはいつでも好きなときに describe が使えます。RSpecの他の機能と同じく、 context はあなたのスペックを読みやすくするためにあります。私が本章でやったように、一つの条件をまとめるために context を使うのも良いですし、アプリケーションのある状態をまとめるために context を使うこともできます。

演習問題

サンプルアプリケーションにモデルのテストをさらに追加する。 私はモデルが持つ一部の機能にしかテストを追加していません。たとえば、Projectモデルのスペックにはバリデーションのスペックが欠けています。それを今、追加してみてください。もしあなたがRSpecを使ってテストできるように設定された自分自身のアプリケーションを持っているなら、そこにもモデルスペックを追加してみてください。

訳者あとがき

(このあとがきは日本語版の初版リリース時に作成されたものです)

伊藤淳一

RailsもRSpecも、日本では比較的ポピュラーなwebフレームワーク/テストツールです。しかし、日本語で書かれたRailsの技術書やRSpecの技術書は発売されていても、「RSpecでRailsをテストする」というテーマだけにフォーカスを当てた日本の技術書はおそらくないと思います。きっと、日本の多くの技術者はweb上に散らばった情報を参考にしたり、職場のメンバーに教えてもらったりしながら、各自で「RSpecでRailsをテストする方法」を模索し続けていたのではないでしょうか。実際、私がそうでしたから。

本書、「Everyday Rails - RSpecによるRailsテスト入門」(原題: Everyday Rails Testing with RSpec )はそんな日本のRailsプログラマの状況をきっと変えてくれる一冊になると私は信じています。非常に初歩的な話から中級者でも知らないような高度なテクニックまで、これほど体系立てて実践的に説明してくれる技術書は他にないからです。本書を読んで内容を理解し、Aaronが言うとおりに自分のアプリケーションで自動テストを組みこんで練習すれば、全くのRSpec初心者でも一気に自動テストのスキルを向上させることができるはずです。目を使って 読む だけではなく、ぜひ自分の手と頭を動かして本書の内容を 身体で理解 してください!

最後に、私の家族へ向けて感謝の気持ちを。スーパーマンではなく、ただの凡人である私は、翻訳の作業時間を作るために家族との時間を削ることぐらいしかできませんでした。妻にも子どもたちにも、ここ数ヶ月はちょっと淋しい思いをさせていたかもしれません。今まで我慢してくれてどうもありがとう。この翻訳の仕事が落ち着いたら、みんなでどこかのんびりと旅行にでも行きましょう!

秋元利春

Rails & RSpecの学習に今から取り組む方にはぜひ読んでほしいと思える一冊に仕上がりました。翻訳チームのメンバーも本書でRSpecを学んでおり、非常に良い学習体験を与えてくれる一冊だと思います。そんな本書の日本語版翻訳に携わることができて本当に嬉しいです。Aaronさん素晴らしい本をありがとう。 そしてこの本を手に取ってくれたあなたに感謝します。あなたに楽しいRuby Lifeが訪れますように。

翻訳作業は想像していた以上に大変な作業でした。翻訳を終えてから過去の技術書を振りかえると、読みやすく伝わりやすい文体で私達の元へ届ける努力をしてくれていた翻訳者の方々の苦労がわかり、彼らには本当に頭が上がらないなと感じました。

とはいえ、確かに大変な作業ではありましたが、得られるものも多かったです。技術的な学習はもちろん、より深く文章の意図やニュアンス、文体のリズムに想いを馳せるきっかけになりました。本当に貴重な経験になりました。

正式版リリース準備に追われて勉強会の最中に翻訳作業にいそしむ私を温かく見守ってくれただけでなく、翻訳に関する相談にも乗ってくれた神戸.rbメンバーのみんな、そして原文解釈に悩んだ際に助けてくれた西脇.rbのマイケルに感謝します。

この翻訳チームの皆さんと一緒に翻訳作業ができて良い経験になりました。翻訳作業自体も大変でしたが、それに付随する諸々のタスクに追われた時にはチームメンバーでサポートしながら問題なく翻訳作業を進められました。伊藤淳一さん、魚振江さんには本当に感謝しています。

最後に。本業や翻訳、コミュニティ活動で忙しくなる私を精神面、体調面で支えてくれている妻と愛猫に感謝します。いつもありがとう。

魚振江

今回、日本語版の翻訳チームに参加できて、そして無事にリリースできてすごく良かったです。これまでの道のりを振り返ってみると、私にとってこの翻訳プロジェクトはなかなか大変なものでした。

まず、私は本書の中国語版をすでに読んでいました。読んだ当時はまだ日本語版がなかったので、本書を日本語で翻訳したいとぼんやりと考えていました。その後、いつも拝読している伊藤さんのブログで英語版の紹介がたびたび出てきて、比較的大きな反響を得ているのを目にしました。そこで実際に翻訳してみようという気持ちになり、2013年10月上旬頃、著者のAaronさんに連絡して翻訳を開始しました。しかし、早くも第1章から長文和訳に苦戦し始め、一人では翻訳作業を思うように進められませんでした。2013年11月頃、伊藤さんに声を掛けて頂き、伊藤さんがプロジェクトをリードする形で翻訳を進めてきました。また、12月からAkiさんも参加してもらい、いろいろと助けていただきました。

このように振り返ってみると、伊藤さんとAkiさんの助けなしでは日本語版の翻訳は全くできなかったと思います。お二人には大変感謝しています。そして、私たちの手で本書の翻訳を完了させたことが、少しでも読者のみなさんのお役に立てば嬉しいです。

日本語版の謝辞

改訂版の謝辞

改訂版の翻訳レビューはソニックガーデンのプログラマである、宋大羽さん木原忠大さん森田高士さん遠藤大介さん安達輝雄さんに協力してもらいました。短いレビュー期間の中で多くのフィードバックを上げてくれたことに感謝します。

初版の謝辞

本書を翻訳するにあたって、お世話になった方々のお名前を挙げさせてください。翻訳者チームの力だけでは本書の翻訳を完成させることは決してできませんでした。

まず、著者のAaronとはFacebookグループやGitHubのIssue上で何度もやりとりを交わしました。忙しい中、毎回丁寧に応対してくれたことを非常に感謝しています。ちなみに、Aaronのラストネームは「サマー(Summer)」ではありません。「サムナー(Sumner)」ですのでお間違いなく。

技術評論社の傳智之さんには技術書を出版する際の進め方についてアドバイスをいただきました。楽天株式会社の藤原大さんには翻訳者としての経験を元に貴重なアドバイスをいただきました。

Leanpubは海外発のサービスということもあって、時々電子書籍中の日本語表示がおかしくなることがありました。そんなときに何度も辛抱強く我々の修正リクエストに応対してくれたLeanpubのMike, Scott, Peterにも感謝しています。おかげでとてもきれいな日本語の電子書籍が完成しました。また、電子書籍で使えそうな日本語フォントを探しているときにTwitterで「あおぞら明朝」の存在を教えてくれたがんじゃさんと、このフォントを作られたbluskisさんにも大変感謝しています。

お忙しい中、ベータ版のレビューをしてくれた橋立友宏さん西川茂伸さん遠藤大介さんにも大変助けられました。どなたも我々だけでは気付かなかった翻訳の問題点や技術的な誤りを指摘していただきました。そして、西脇.rbのイギリス人プログラマ、マイケル(P. Michael Holland)はほとんど4人目の翻訳者と呼んでも良いぐらいの活躍をしてもらいました。彼が英語と日本語の橋渡しをしてくれていなければ、この翻訳がもっともっと辛い作業になっていたことは間違いありません。

最後に、本書を購入してくださったみなさんに感謝します。本書のベータ版を発売する前は、こんなにたくさんの方が本書を購入して下さるとは思いませんでした。翻訳者一同、本当に感謝しています。また、「本書を読んだおかげでRailsのテスト力が上がった」なんていう声があちこちから聞こえてくることを楽しみにしています。ぜひ、ご自身のTwitterやブログ等で感想を聞かせて下さい。ご意見やご質問でも構いません。本書は引き続きバージョンアップを繰り返していく予定です。「読み終わったからこれでおしまい」ではなく、今後もまたみなさんと紙面で(画面で?)再会できることを楽しみにしています。ではそのときまで、ごきげんよう。

 

翻訳者一同

Everyday Railsについて

Everyday Rails はRuby on Railsに関するTipsやアイデアを紹介するブログです。あなたのアプリケーション開発に役立つ素晴らしいツールやテクニック等も紹介しています。Everyday RailsのURLはこちらです。https://everydayrails.com/

著者について

Aaron Sumner は20年以上Webアプリケーションを開発しています。その間彼はCGIをAppleScriptで(本当です)、Perlで、PHPで、そしてRubyとRailsで作ってきました。仕事を終えてテキストエディタの前から離れると、Aaronは写真や野球(Cardinalsを応援しています)、カレッジスポーツ(カンザス大学Jayhawksのファンです)、アウトドアクッキング、木工制作、ボーリングなどを楽しんでいます。彼は妻のEliseと5匹の猫、それに1匹の犬と一緒に、カンザスの田舎に住んでいます。

Aaronの個人ブログは https://www.aaronsumner.com/ です。 「Everyday Rails - RSpecによるRails テスト入門」(原題: Everyday Rails Testing with RSpec )は彼が書いた最初の本です。

訳者紹介

伊藤淳一

株式会社ソニックガーデンに勤務するRailsプログラマ。Rubyコミュニティ西脇.rbの主催者として神戸近辺でRuby勉強会も開催している。ブログQiitaなどで公開したプログラミング関連の記事多数。著書に「プロを目指す人のためのRuby入門」(技術評論社)がある。 Twitterアカウントは@jnchito

秋元利春

Kumu Inc 代表。神戸で活動するRailsプログラマ。神戸.rbや 初心者向けに有志でRuby, Railsを教え合うRails Follow-up Kobeを主催。Rails Girls Japanのorganizer, coachも務める。Twitterアカウントは@spring_aki

魚振江

Railsプログラマ。Yokohama.rbと東京近くのRuby/Railsコミュニティで活動しています。Twitterアカウントは@blueplanet42

カバーの説明

カバーで使用した実用的で信頼性の高そうな赤いピックアップトラックの写真はiStockphotoの投稿者であるHabman_18によって撮影されたものです。私は長い時間をかけて(もしかすると長すぎたかも)カバー用の写真を探しました。私がこの写真を選んだ理由は、この写真がRailsのテストに対する私の姿勢を表していると思ったからです。つまり、どちらも派手ではなく、目的地に到達する最速の手段になるとは限りませんが、頑丈で頼りになります。そして、この車はRubyのような赤色です。いや、もしかすると緑色の方が良かったかもしれません。スペックがパスするときの色みたいに。むむむ。

変更履歴

2018/03/31
  • 翻訳の見直し・改善
  • 誤字脱字の修正
  • シンタックスハイライトや行番号のフォーマット修正
2018/02/21
  • Rails 5.1 + RSpec 3.6版に全面改訂。(原著の2017/11/27版に追従)
2017/05/05
  • 第5章の「整理」で提示したコードを実装コードと一致するように修正。
2017/04/29
  • 第4章と第5章にあった誤記を修正。
2017/03/21
  • 「追加コンテンツ「RSpecユーザのためのMinitestチュートリアル」について」にあったtypoを修正。
2016/10/10
  • 第5章の細かい文章表現を改善。
2016/06/08
  • 第10章にあったtypoを修正。
2015/09/15
  • 第9章にあったtypoを修正。
2015/06/30
  • 追加コンテンツ「RSpecユーザのためのMinitestチュートリアル」に関する説明を追加。
2014/12/29
  • 原著の2014-12-19版に追従。
    • binstubを使用するサンプルコードの修正
    • DatabaseCleanerに関する説明をアップデート(第8章)
    • タグに関する情報を追加(第9章)
    • pending から skip への変更とその理由の説明(第9章)
    • ファイルアップロードのサンプルコードを修正(第10章)
    • APIのテストで have_http_status マッチャを使うように変更(第10章)
    • rails scaffold を使用する際に、不要なassetsやヘルパーを作成しない方法を説明(第11章)
    • 情報源リストのアップデート
    • その他、細かい文章の改善
2014/11/23
  • 第3章にあった軽微な翻訳ミスを修正。
2014/10/24
  • RSpec 3.xとRails 4.1に対応したメジャーアップデート版を公開。
  • 第10章に外部サービスのテスト、APIのテスト等を加筆.
  • RSpec 2.99に関する章を削除。(必要であれば一つ前の版をダウンロードしてください。)
  • その他、細かい情報のアップデートや表現の修正等を実施。
2014/07/17
  • iPad版のKindleで表示したときに、鍵マークやエクスクラメーションマークのアイコンが大きく表示される問題を修正。
2014/05/22
  • 第12章「RSpec 3に向けて」を新しく追加。
2014/04/22
  • 第4章にあった軽微な誤字を修正。
2014/04/17
  • 第5章にあった軽微な誤字を修正。
2014/02/28
  • 正式版第1版作成。
  • 「サンプル」の訳を「example」に変更。
  • 「共有サンプル」の訳を「shared examples」に変更。
  • 「テストの主語」となっていた箇所を「テストの対象」に変更。
  • 訳者あとがき、日本語版の謝辞、および訳者紹介のページを追加。
  • 翻訳全体に関して、日本語としての読みやすさを改善。
  • 誤字脱字、表記の揺れ、フォーマット崩れ、段落先頭の字下げの文字数、シンタックスハイライトの不一致等を修正。
  • 原著に合わせる形でサンプルコードに行番号を表示(表示されていない部分は原著通り)。
  • 原著の2014-02-24版に追従。
    • selenium-webdriver gemのバージョンを 2.35.1 から 2.39 に変更(第2章)。
    • 場所の重要性が本書の後半で重要になる理由を追記(第3章)。
    • eqinclude が定義されているgemに関する情報の誤りを修正(第3章)。
    • before メソッドの初期値に関する説明を追加(第3章)。
    • /contacts/:contact_id/phones/:id のパスが phone になっていたミスを修正(第5章)。
    • カスタムマッチャの定義例に関するリンクを変更(第7章)。
    • Seleniumの依存関係に関する説明を追記(第8章)。
    • Guardを使ったCSSコンパイルがSassやLESSを指していることを追記(第9章)。
    • モックのサンプルコードで before ブロックを2回記述してたミスを修正(第9章)。
    • 「古い習慣に戻らないでください!」のセクションで抜けていた後半部分の記述を追加(第12章)。
    • Relishが古いバージョンのRSpecをサポートしなくなったため、「RelishのRSpecドキュメント」のセクションにあった後半の記述を削除(Railsのテストの関するさらなる情報源)。
    • いくつかのサンプルコードにおいて小規模なリファクタリングを実施。
2014/02/10
  • rspec-rails のtypoを修正(第2章)。
  • PDFで見た場合に、一部の文章がページの外にはみ出してしまう問題を修正(第2章、およびRailsのテストの関するさらなる情報源)。
2014/02/07
  • ベータ版第1版作成(原著の2013/10/07版を翻訳)。