Hatena::ブログ(Diary)

アジャイルSEを目指すブログ

2012-08-20

私はRSpecでテストをこんな感じで書いてる

私がRSpec使ってテスト書く時はこんな感じで書いてるよ〜ってのを書いてみた。*1

テストを書く順番について

TDDでコードを書く場合、先にテストを書く事になります。

そして、そのテストを書く順番ですが、私は下記のような順番で書くように意識しています。

  1. 設計する
  2. describe を書く
  3. itを書く
  4. subjectを明確にする
  5. before(context)を明確にする

その他に、気をつけている点はこんな感じ

  • 別のメソッド呼ぶ時は基本的にstubなどで潰す
  • contextは「〜の場合」、it は「〜であること」になるようにする

一つずつ、詳細を書きます。

設計する

テストを書き始める前に、まず実装しようとしてるクラス、メソッドを簡単に設計します。

少なくとも、「クラス名」「クラスメソッド or インスタンスメソッド」「メソッド名」「メソッド戻り値」ぐらいは決めます。

describe を書く

設計したクラス、メソッド名に合わせてテストを書き始めます。

インスタンスメソッドの場合は下記のように "#〜" で書きます。クラスメソッドの場合は".〜"になるようにしてます。

+# -*- coding: utf-8 -*-
+require_relative 'user'
+
+describe User do
+  describe "#admin?" do
+  end
+end

詳細は後述しますが、これはrspecの実行結果で「それっぽい」出力にするため。

it を書く

まず、「◯◯が管理者であること」のように"結果"から考える。

で、それをspecに書く。

# -*- coding: utf-8 -*-
require_relative 'user'

describe User do
  describe "#admin?" do
+    it { should be_admin }
  end
end

subject を明確にする

次に、「◯◯」の部分を明確にする。

「管理者のユーザ」として、specに書いてみる。

# -*- coding: utf-8 -*-
require_relative 'user'

describe User do
  describe "#admin?" do
+    subject { @admin_user }
    it { should be_admin }
  end
end

before(context) を明確にする

subject で指定している @admin_user をbefore で用意する。

# -*- coding: utf-8 -*-
require_relative 'user'

describe User do
  describe "#admin?" do
+    before { @admin_user = User.new(role: 'admin') }
+
    subject { @admin_user }
    it { should be_admin }
  end
end

他のパターンも作成し、context を分ける。

ここで、context の内容が、before と一致するように気をつける。

 require_relative 'user'

describe User do
  describe "#admin?" do
-    before { @admin_user = User.new(role: 'admin') }
+    context "管理者の場合" do
+      before { @admin_user = User.new(role: 'admin') }
+
+      subject { @admin_user }
+      it { should be_admin }
+    end
+
+    context "一般ユーザの場合" do
+      before { @user = User.new(role: nil) }
+
+      subject { @user }
+      it { should_not be_admin }
+    end
 
-    subject { @admin_user }
-    it { should be_admin }
  end
end

別のメソッド呼ぶ時は基本的にstubなどで潰す

例えば「管理者のリンディさんだけシステムを起動できる」という機能を作る必要があり、テストを書く場合、admin? のテストは済んでいるため、stub!で潰します。

# -*- coding: utf-8 -*-
require_relative 'user'

describe User do
  describe "#admin?" do
    context "管理者の場合" do
      before { @admin_user = User.new(role: 'admin') }

      subject { @admin_user }
      it { should be_admin }
    end

    context "一般ユーザの場合" do
      before { @user = User.new(role: nil) }

      subject { @user }
      it { should_not be_admin }
    end
  end
+
+  describe "#runnable_system?" do
+     context "管理者がリンディさんの場合" do
+      before do
+        @lindi = User.new(name: 'Lindi')
+        @lindi.stub!(admin?: true)
+      end
+
+      subject { @lindi }
+      it { should be_runnable_system }
+    end
+  end
end

このようにしておく事で、仮に管理者の判定処理( User#admin? )が「管理者はroleが'admin'、もしくはskillが'SS'」みたいな仕様に変わっても、このテストはfailしなくなります。

contextは「〜の場合」、it は「〜であること」になるようにする

まず始めに、この記事で例として書いていたテストを普通に実行した時の出力を紹介します。

$ rspec user_spec.rb
...

Finished in 0.00216 seconds
3 examples, 0 failures

このままでもrspecは実行出来るのですが、オプションで format を指定すると、次のようになります。

  • f d は --format documentation と同じです。
$ rspec -f d user_spec.rb

User
  #admin?
    管理者の場合
      should be admin
    一般ユーザの場合
      should not be admin
  #runnable_system?
    管理者がリンディさんの場合
      should be runnable system

Finished in 0.00236 seconds
3 examples, 0 failures

こんな感じで「◯◯は、△△の場合、〜〜であること」の形になるように意識します。

例: User#admin?のメソッド は、管理者 の場合、admin であること

  • ◯◯:メソッド
  • △△:状況(context)
  • 〜〜:結果

まとめ

「私がどう書いているか?」を書いただけなので、このやり方が正しいとかではないです。*2

ただ、「itから書いて〜」という私のような書き方が少し検索しても見当たらなかったので、何かの参考になるかなーと書いてみました。

以下、リンク集

はじめに注意点を一つ。

ruby周りのwebの情報は情報が古くなっている事が多いです。出来るだけ、英語の公式ドキュメントやソースコードも読む癖をつけた方が良いです。

あと、新しい情報を探す時に「rails capistrano 2012」のように、調べたい内容の最後に西暦を入れると新しい情報が見つけやすいです。普通に検索した後、念のため最近使った人のブログ記事を検索し、目を通しておく事をおすすめします。

rspecについて分からない事があったら

公式サイトを調べる

RDocを調べる

写経してみる

t-wada さんの記事がとても参考になる

ただ、この記事はruby 1.8rspec 1.3.0の内容なので、その点には注意する必要がある。

下記に気づいた点を記載しておく。

No command 'spec'

"spec"コマンドではなく、"rspec"コマンドを使用します。

$ rspec message_filter_spec.rb 
cannot load such file -- message_filter

ruby 1.9.2以降だとロードパスにカレントディレクトリが含まれなくなったため、エラーが起きます。

require_relative を使うようにします。

require_relative 'message_filter'

RSpecについてもっと詳しく知りたい

@ukstudio さんのるびまの記事を読む

RspecRailsで使う時の事が分からない

rspec-railsのREADMEを読む

web上の他の資料

rails 3.2.xの情報。

rails2.x の頃の情報なので、一部古いので注意

参考書籍

*1:複雑なテストはここまでシンプルに書けてないですが(´・ω・)

*2:むしろ、RSpecの正しい書き方とか私が知りたいです

ToMmYToMmY 2012/08/22 09:39 ご存知とは思いますが、私は named subject が好きです。

>||
before { @user = User.new(role: nil) }
subject { @user }
||<



>||
subject(user) { User.new(role: nil) }
||<

sinsokusinsoku 2012/08/28 01:11 > ToMmY さん
コメントありがとうございます!

実はnamed subject はあまりご存知でも無かったりw
named_subject, shared_context 辺りはあまり詳しくないので、もっとrspec頑張ります(`・ω・´)

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/sinsoku/20120820/1345470914
リンク元