Web備忘録

雑多に書いています

「EFFECTIVE TESTING WITH RSPEC 3: BUILD RUBY APPS WITH CONFIDENCE」の1章を読んだ

あとから読みなおせるようにブログにまとめつつ読んでいます。

Effective Testing with RSpec 3: Build Ruby Apps with Confidence

Effective Testing with RSpec 3: Build Ruby Apps with Confidence

EFFECTIVE TESTING WITH RSPEC 3: BUILD RUBY APPS WITH CONFIDENCE 

は、直訳すると「RSpec3による効率的なテスト: 自信をもってRubyのアプリケーションを組み立てる」みたいな感じでしょうか。

英語版で読む理由

  1. 翻訳されてない
  2. 英語学習のため
  3. 英語で体系的な学習をする練習をするため

注意点

自分の解釈だったり誤訳も含んで日本語にしています。

序文(Foreword)

ソフトウェア開発は覚えることがたくさんあって大変だ。テストを書くこともそのうちの一つかもしれない。

しかし、ソフトウェア開発にテストを書くことは、欠かせない。

そして(Railsで)よいテストコードを書くためにRSpecは欠かせない。

RSpecは単なるテストフレームワークでなく、批判的に、根気よく、体系的に自分のコードの設計について考える方法と、体系的な手法でソフトウェアを開発する方法を学ぶためのツールでもある。

はじめに(Introduction)

「俺らのテストがまた落ちたよ!」

「なんでこの一連のコードが通るまでにこんなに時間がかかるんだ?」

「こんなテストになんの価値があるんだろう?」

テクノロジーは変わったけどテストへの不満の内容は変わらない。

この本では「効果的な(EFFECTIVE)」なテストの書き方を教えるよ。

効果的っていうのは、「わざわざ時間を割いて書く意味がある」ってことだ。

1章 RSpecをはじめよう
(Chp1 Getting Started With RSpec)

RSpecは生産的なテストフレームワークだ。スタイル、API、ライブラリ、設定、それら全てが最高のソフトウェア開発を手助けしてくれる。

いいテストから得られる効果は少なくともこれだけある。

最初のSpec

サンドウィッチを例にあげて考える。

RSpecはdescribeとitによって、まるで会話文のように表現することができる。

"Describe an ideal sandwich" (理想的なサンドウィッチについて説明してくれ)

"First, it is delicious" (はじめに、それはおいしい)

RSpecではこう書く。

RSpec.describe 'An ideal sandwich' do
  it 'is delicious' do
  end
end

テストケースを加えたものが以下だ。

RSpec.describe 'An ideal sandwich' do
  it 'is delicious' do
    sandwich = Sandwich.new('delicious', [])

    taste = sandwich.taste

    expect(taste).to eq('delicious')
  end
end

specはspecification(仕様書)の短縮形だ。

RSpec.describe ブロックは「例の集合*1」をつくる。

例の集合が何をテストしているか明らかにして、(今回だとサンドウィッチ)、関連するspecsを一つにまとめるんだ。

ネストされたブロック 'is delicious' はサンドウィッチの使い方の例だ。(他のフレームワークだとテストケースって呼ぶかもしれない)

コラム: テスト対スペック対イグザンプル (Tests vs. Specs vs. Examples)

テストはコードが正確に動くかvalidate(検証)する

スペックはコードに振る舞ってほしい動きをdescribe(説明)する

イグザンプルは特定のAPIがどのように使われるかを明らかにする

さて、ここでおさらいしておこう。

  • RSpec.describe が例の集合をつくる
  • it が例単体をつくる
  • expect が期待される結果をあらわす

この3つがRSpecの核だ。どんなに長い道のりも、この3つを使えば前には進むことが出来る。

また、サンドウィッチspecから得られる目的は2つ。

  • サンドウイッチに振る舞ってほしい挙動のドキュメント化
  • サンドウィッチがあるべき姿かどうかのチェック

サンドウィッチspecのよいところは、「あたらしくプロジェクトに参画した人」のドキュメントになるってことだ。 specを読めば、サンドウィッチはおいしくなきゃならないってのがすぐにわかる。

セットアップの共有(Sharing Setup)

  • RSpecフックはテストの間、自動的に特定の回数実行される。
  • ヘルパーメソッドはRubyの標準メソッドで、いつ実行されるかコントロールできる。
  • letは必要に応じてデータを初期化する。

一つずつ順に見ていこう。

RSpecフック(before)

beforeは、itごとに準備されるRSpecフックだ。

Sandwich = Struct.new(:taste, :toppings)
RSpec.describe 'An ideal sandwich' do

  before { @sandwich = Sandwich.new('delicious', [])}

  it 'is delicious' do
    taste = @sandwich.taste

    expect(taste).to eq('delicious')
  end

  it 'let me add toppings' do
    @sandwich.toppings << 'cheese'
    toppings = @sandwich.toppings

    expect(toppings).not_to be_empty
  end

end

ここでは、@sandwichを使ったけど、インスタンス変数には実は欠点もある。

  1. もし@sandwichをミススペルして参照しても、Rubyは暗黙的にnilを返す。このことが、エラーの原因が単なるタイポであることをエラー文から読み取りづらくする。
  2. もしsandwichという変数を、@sandwichというインスタンス変数に置き換えたい場合は、ファイル全体で置き換える必要がある。
  3. beforeフックでインスタンス変数を使うと、必要ないときも@sandwichを生み出し、セットアップのためにコストがかかる。これは大規模プロジェクトではときに見逃せない不効率性になる。

別のアプローチを試してみよう。

ヘルパーメソッド

RSpecは便利だから、ときにそれがRuby支配下であることを忘れるけど、メソッドを定義することだって可能なんだ。

def sandwich
  @sandwich ||= Sandwich.new('delicious', [])
end

このようなテクニックはメモ化(memoization)*2と呼ぶ。

この手法は便利だけど、落とし穴がないわけじゃない。||=は@sandwichがnilかfalseのときに働くから、@sandwichにnilかfalseを格納することはできなくなる。

こういった問題を解決する手段として、RSpecにはletが用意されている。

letでオブジェクトを共有する(Sharing Objects with let)

さきほどのsandwichメソッドを以下のコードに置き換えてみよう。

let(:sandwich) { Sandwich.new('delicious', [])}

letは「sandwichという名前と、ブロック{}を結びつけるもの」と考えてもいい。 このletによって、ちょうど先程のメモ化したヘルパーメソッドのように、それぞれのイグザンプルはsandwichが呼ばれた「最初のとき」だけブロックを評価するんだ。

コード共有テクニックを使うさいの注意点

これらのテクニックは、保守性を向上させ、ノイズを減らし、コードを明瞭にするために使おう。

あなたの番(Your Turn)

エクササイズ

  1. RSpecにおけるコードの重複を防ぐための3つの主要な手法(フック、ヘルパーメソッド、let)を紹介したけど、今回のケースだとどれが一番よいと思う? そしてそれはなぜだろう? そして、あなたが選択しなかった手法が、他の状況では望ましくなるのはどんなときだろう?

  2. rspec --help を実行し、いくつかsandwichイグザンプルで試してみてほしい。

さて、RSpecのお気に入りの使い方と出会う準備は出来てる? 

サンドウィッチ休憩をとったら、次のチャプターで会おう。

エクササイズと自分の感想

エクササイズ1をやってみた

1はbeforeでよさそうだな〜。@sandwichの各属性に対するテストだからitごとにインスタンス変数を準備しておくことに違和感を感じない。

複数のインスタンスを複合的に使ったりする場面ではletのがいいのかも?

エクササイズ2をやってみた

rspec -e STRINGを実行した。 STRINGに指定した文字列に対して、itの後ろの文字列( 'is delicious'等)がマッチしたものだけテストが走る。 ヘルプに「何回も使うかもね」って書いてた。

感想

読んで訳してブログにするのに4時間ぐらいかかったーー。 作業自体は(疲れるけど)楽しいから、ちょっと頭の体操したいときの息抜きかなあ。 そして、著者がRSpec大好きなことが伝わってきて読んでて面白い。

頑張って2章もやりたい…!!

*1:example group

*2:Wikipedia: メモ化(英: Memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。