Rails Developers Meetup 2017 で RSpec しぐさについて話した

Rails Developers Meetup の年末拡大版である、Rails Developers Meetup 2017 で発表させていただきました。

RSpec をどう書いていくと良いのかの指針、みたいな話です。資料はこちら

RSpecしぐさ from Takafumi ONAKA

スライドを作るにあたって考えたこと

「5 分では RSpec の こう書くべき という話にたどり着けない」が最初の山でした。 考えてみれば 第 1 回 Rails Developers Meetupwillnet さんの話 が同じ題材 (RSpec の書き方) で、35 分枠だったので当然ですね。

そこで伝えたいことを絞ることにしました。どうせ聴衆は

辺りの「綺麗な RSpec の書き方」は見ているだろうから、それらと、ぼくのかんがえたさいきょうの RSpec の書き方との「違い」にフォーカスすれば良いやと。 (でも古い記事が多いので、実はあまり見てないかもしれないなーと思ったのでスライド末尾に URL を載せたのでした)

というわけで伝えたいことを 3 つに絞りました。

です。

BDD って何?

スライド中にも書きましたが、

BDD とは、TDD を「テスト」を使わないように Dan North が発明した「言い換え」

です。

スはスペックのス においても

FAQ:「RSpec って、要は Test::Unit でやっていることを別の書き方にしただけでは?」

この FAQ への短い答えはイエスです。 しかし、その「書き方」が人の思考を変えてしまうことを、Rubyist の皆さんはよく知っている と思います。

とあります。

BDD が定義した語彙を使うと自然に仕様を表現できるようになったので、 テスト駆動開発は xUnit の一歩先の世界にたどり着けたのだと考えています。

まずはこの衝撃を伝えたかった。

Given-When-Then という構造化技法

RSpec/Cucumber の中で発明された語彙の中に GivenWhenThen というものがあります。 普段の開発の中では Cucumber のシナリオを書いているときにしか出てきませんが、これが BDD の、振る舞いを記述するための語彙だと僕は認識しています。

振る舞いを記述するために Given-When-Then という構造化技法があり、これが Arrange-Act-Assert とは違う語彙だからこそ生み出せる書き味というものがあります。スライド中に言及したネストや順序を問わない部分がまさにそれですね。(参考: Given-When-Then - 日々ごちゃごちゃと考える)

当時の Test::Unit には階層化のメソッドが無かったので、ネストで状況を表現できる context は本当に画期的だったんです。(のちに sub_test_case として輸入されて、ネストに関しては差が無くなりました。 test-unit 2.5.2 リリースアナウンス)

それなのに今では rubocop-rspec が検出する ぐらい「let を使うべき」という書き方が一般化しています。RSpec のリードメンテナである Myron Marston 氏も以下のように述べています。

ruby on rails - When to use RSpec let()? - Stack Overflow (日本語訳)

私は以下のような理由からインスタンス変数ではなく let をいつも使っています。

  1. typoにすぐ気づける
  2. 無駄な初期化の時間を無くせる
  3. ローカル変数をそのままletに置き換えられる
  4. letを使った方が読みやすい

スライド中では「音楽性の違い」と表現しましたが、BDD の語彙だからこそ生み出せた何かが、たかが typo の検出、たかが遅延評価のために無くなってしまってはいませんか。

typo の検出にメリットがあるのは分かりますが、それでも let の乱用は書きづらさ、読みづらさを生み出していると、僕は思います。圧倒的に「状況を表現すること」の方に分があります。インスタンス変数でも使って良いんですよ。

describe "#male?" do
  subject { @user.male? }
  before { @user = create(:user, gender: gender) }

  context "when male" do
    let(:gender) { User::Gender::MALE }
    it { should eq true }
  end

  context "when female" do
    let(:gender) { User::Gender::FEMALE }
    it { should eq false }
  end
end

beforelet(:user) だったとしたら、「事前条件として user が存在すること」を表現するコードが存在しなくなっちゃうじゃないですか。

ちなみに rspec-given っていう gem があって、 これを使うと rspec を Given, When, Then で書けます。minitest も対応しているし、Natural Assertions は power-assert 以前の時代では最高に書きやすかった。

2013 年ぐらいは生き生きとしていて、かなり方向性が一致しているなーと思いながら手元で使っていた (チームに強要するほどは傾倒できなかった) んだけど、最近は元気ないですねー……。

should 記法という破れた夢

ここも音楽性の違いが如実に現れているところなんですよね。

RubyKaigi 2017 で右代入の話があったのを覚えていますでしょうか? 左から右に流れるように書きたいから、右に向かって代入したい。

「流れるように書きたい」という理由で言語仕様を考えるぐらいの Rubyist に対して、

foo.should == bar

と流れるように書いていたコードを

expect(foo).to eq bar

と書くように強制してきたんですよ。これはもう戦争でしょ! という怒りの表明です。

我々は should 記法をまだ取り上げられたわけではないので、使っても良いんですよ……! 少なくともワンライナーの should にはモンキーパッチが使われていないので、言い訳することなく正々堂々と使えるのです。

AMA: The authors of "Effective Testing with RSpec 3", Myron Marston and Ian Dees : ruby (日本語訳) の

私は subject やワンライナー構文(例: it { is_expected...} )を使うことはほとんどありません。

についても、「お前に should 記法を deprecated にされたから仕方なくワンライナーで書いとるんやんけ」という気持ちが湧いてきますね。

-foo.stub(bar: buz)
+allow(foo).to receive(:bar).and_return(baz)

も流れるように感が減ってますよね。

allow().to receive
expect().to receive
expect().to have_received

で揃うようになったので綺麗ではあるんだけど。

夢を託していた当時の様子は

辺りを読んでもらうと見えるんじゃないかと思います。

くそー、ホントあの瞬間に Refinements があればなー……。

……2012年6月に Refinements があれば RSpec は——

BDD を体現するテストライブラリとしての位置を確立し続けただろう。

でも、そうはならなかった。ならなかったんだよ、ロック。

だから——この話はここでお終いなんだ。

まとめ

というわけで

という話をしました。

前 2 つは 新訳版 テスト駆動開発 の『付録 C』に書いてあるのでサラッと流して should 記法の繰り言に持ち込もうと思っていたんですが、思った以上に『付録 C』を読んでいる人が居なかった (会場の 1/3 ぐらいでした) ので説明を丁寧にしていたら時間切れになってしまいました><

テスト駆動開発 書影

本当に TDD をとりまくここ 20 年の動きについてよくまとまっている文章なので、一度は読んでみてください。