RSpecの恐るべき強制力

最近ちょっとRubyでプログラムを開発することがありました.その過程でせっかくだからとRSpecを使ってやってみて,思ったことを書きます.
RSpec本などは読んでないので,BDDのお話には深入りしません.mock/stubも出て来ません.

一応RSpecについて説明

知ってる人はスキップして大丈夫です.

RSpecというのは,Ruby向けの振舞駆動開発 (BDD; Behavior Driven Development) フレームワーク.手早く言うとBDDはテスト駆動開発のサブセットで,RSpecは書き方が特殊なユニットテストフレームワークです.

例えば,言語組み込みのStringクラスを部分的にテストするコードをRSpecで書いてみると,

describe String do
  before do
    @str = "Hello"
  end
  describe "#size" do
    it "should return its length" do
      @str.size.should == 5
    end
  end
  describe "#*" do
    it "should return empty string when zero given" do
      (@str*0).should == ""
    end
    it "repeats self string for given n times" do
      (@str*2).should == "HelloHello"
    end
  end
end

のような感じになります.

Rubyが分からない人のために,do〜〜endはRubyではお馴染みのブロック渡しというもので,中に書いた処理をメソッドの引数にしていることを説明しておきます.
itメソッドがテストケースの一つを表していて,describeメソッドを使ってテストケースを分類することができます.クラスや文字列を引数に渡していますが,これはどういうことをテストするのかを説明したコメントのようなものです.

@str.size.should == 5

のように書いているのが,いわゆるアサーション.この例は「@str.sizeは5であるべき」と言って確かめさせるためのコードです.*1

これをstring_spec.rbという名前で保存して,

$ rspec string_spec.rb

を実行すると,

...

Finished in 0.00055 seconds
3 examples, 0 failures

のようにテストが実行できます.

もしテストが失敗したときはどうなるか.今回の例で@str*2を@str*3へと変更してわざとテストを失敗させると

..F

Failures:

  1) String#* repeats self string for given n times
     Failure/Error: (@str*3).should == "HelloHello"
       expected: "HelloHello"
            got: "HelloHelloHello" (using ==)
     # ./string_spec.rb:15:in `block (3 levels) in '

Finished in 0.00067 seconds
3 examples, 1 failure

Failed examples:

rspec ./string_spec.rb:14 # String#* repeats itself for given n times

のように表示されます.

  1) String#* repeats self string for given n times

なにやら英文らしきものが表示されていますよね.これは,さっきほどのコードに書いたdescribeメソッドとitメソッドに付けた引数を繋げただけのものです.もちろん,こう繋がることを意図して元のコードを書いたのですが.

describeメソッドは「これから〜〜について記述します」という風に,テストするクラスやメソッドの名前,あるいはその背景を引数に書きます.

describe String, "when initialized" do

いっぽう,一つ一つのテストケースではそれが何を確かめるものなのかを表すために,望ましい振舞の内容をitメソッドの引数に書くわけです.

it "should be empty" do

この決まりを守ってテストを書くと上で示したように自然に繋がった英語が出てくると思います.

長々と書きましたが,次からが本題です.

ユーザにテストの正しい書き方を強いるRSpec

RSpecのサンプルを見たときから,僕は当然のように

@array.size.should == 1

みたいにしてアサーションを書いてきたわけですが,これは

1.should == @array.size

という書き方も出来るのではと気付きました.もちろんこれでも動きます.ですが,英語の知識が少しあるとこの書き方は不自然だと思うでしょう.
実際、これは間違ったRSpecの書き方です.
あまつさえ,この状態でテストに失敗すると

expected: 6
     got: 5 (using ==)

などと表示されるわけです.後者の書き方が間違っていることはもはや疑う余地がありません.

何が言いたいかと言うと,RSpecは人が英語の知識を持っていることを利用してアサーションの書き方を強制しているということ*2
他のテストフレームワークだとおそらくこんな感じに.

assert_equal(@str.size, 5)
assert_equal(5, @str.size)

どちらで書いてもあまり気になりません.フレームワーク側としてはどちらを正しいとしているのか,それともどちらが正しいか決まっていないのか.この書き方からはそれが全くわかりません.
RSpecは,主述の関係を明示させるような書き方にすることで表記の揺れが起こらないようにしているわけです.

itメソッドやdescribeメソッドについてもそう.引数は日本語で書いても機能的には問題ないですが,余計な英語の知識がRSpecを正しく使うよう迫ってきます.
さきほど,RSpecの説明で

itメソッドがテストケースの一つを表していて,describeメソッドを使ってテストケースを分類することができます.

と書きましたが,こういう端的な説明では「普通のxUnitと何が違うのだろうか」とか思いますよね.実際,ユニットテストフレームワークとしての機能だけを見ればRSpecはxUnitとそれほど変わりません.ですが,使い方をここまで強制してくるという点でRSpecは普通のxUnitとは一味も二味も違います.

名前が悪いんですよ名前が.”it"なんて命名されたらこう書かざるを得ないじゃないですか! (怒)

RSpecの記法にはセマンティクスが反映されている

この手のフレームワークやツールをユーザに正しく使ってもらうには,機能を単純に用意してとにかくドキュメントで使い方を細かく指導するというのが古くからあるやり方だったと思います.それに対してインタフェースを工夫して正しい使い方を示唆させるやり方がユーザにとってはわかりやすいし,何より忘れにくい.そういったわかりやすいインタフェースは,フレームワークの機能のセマンティクス (意味) をよく反映したものなんですよね.もちろん,そのフレームワークが結局どういうものなのかが考察・整理されることがそういうインタフェースを作るのに必要です.
xUnitではassert〜〜みたいに味気ない名前だったものから"○○ should △△"という意味が見出されてBDDが成立し,RSpecのようにcoolな記法が作られたと.xUnitは,モジュールをテストする機能から出発しているわけですから,初発のフレームワークが味気ないインタフェースを提供するのは仕方がないかなと思います.RSpec,というかBDDの出現はテスト駆動開発が成熟してきていることを表しているんでしょうね.それだけ深く考察されてきたということなのですから.

RSpecが登場してもう数年.時代遅れ学生の今更なベタ誉めエントリーでした.

*1: 「@str.sizeは5でないべき」と書きたい場合はshouldの代わりにshould_notを使います.

*2: もちろん,僕が勝手に強制されているのですが