俺のRSpecがこんなに雑なわけがない

ここ最近、 RSpec の書き方が自分の中で変化したので、そのことを書きます。（こういう話ちょっと今更なきもしますが...）
私の書き方は少数派だろうと思います。他の人に強く勧めるつもりもありません。
ただ、どうにも [Better Spec](http://betterspecs.org/jp/) のような書き方には疲れました。
テストを書くということはもっと気楽なものでいいように思います。
こういう書き方もあるんだよ、という程度で読んでもらえれば幸いです。

## 概要

ほんの一年くらい前まで [Better Spec](http://betterspecs.org/jp/) みたいな RSpec の書き方を私も心がけていました。
`describe` と `context` を使い分け、`subject` と `it` をうまいこと組み合わせてテストを作っていました。

簡単な例ですが、次みたいな感じです。

```ruby
# 以下、このテストを「ケースA」と呼びます。
describe 'Book' do
  describe '#valid?' do
    subject { book.valid? }

    context '必須項目を入力するとき' do
      let(:book) { Book.new(title: '孤独のグルメ 【新装版】', author: '久住 昌之・谷口 ジロー') }

      it { is_expected.to be_truthy }
    end

    context '必須項目を入力しないとき' do
      let(:book) { Book.new }

      it { is_expected.to be_falsey }

      # subjectと異なるメソッドをテストするときはitではなくspecifyを使う
      specify 'エラーメッセージがあること' do
        expect(book.errors.full_messages).to contain_exactly(
          'タイトルを入力して下さい',
          '筆者を入力して下さい'
        )
      end
    end
  end
end
```

3年くらいこの書き方が読みやすいと思っていたんですが、最近は次のような感じです。

```ruby
# 以下、このテストを「ケースB」と呼びます。
describe 'Book' do
  describe '#valid?' do
    example '必須項目を入力するケース' do
      book = Book.new(title: '孤独のグルメ 【新装版】', author: '久住 昌之・谷口 ジロー')

      # is_asserted_by は https://github.com/joker1007/rspec-power_assert のメソッド
      is_asserted_by { book.valid? }
    end

    example '必須項目を入力しないケース' do
      book = Book.new

      is_asserted_by { !book.valid? }
      is_asserted_by { book.errors.full_messages.include?('タイトルを入力して下さい') }
      is_asserted_by { book.errors.full_messages.include?('筆者を入力して下さい') }
    end
  end
end
```

**雑！！**

`subject` や `context` でテストを説明していた前者に比べて、後者は `example` だけです。
(念の為、 `example` と `it` と `specify` は同じメソッドです)
なんとなく、後者は雑に見えませんか？
前者の書き方に慣れていた私には雑に見えます。しかし、私はこの書き方をしています。

## 雑なRSpecの書き方

次のようなことを気をつけて書いています。

- `it` メソッドは `example` を使う
- `describe` と `context` によるネストは最小限にする
- コードを DRY にするために `let` や `before` を使う

### やりたいことは何か？

やりたいことは `example` 句を読めば理解できるテストを作ることです。

ケースAの書き方で私が不満なことは、目線が上下に動くことです。
私の目線は `context` と `let` を読んだ後 `it` を読む時に一度 `subject` に戻ります。
短いテストならいいですが、長くなってくると辛いです。

もうひとつケースAで不満なことは、テストケースを追加する時に、ネスト構造を意識する必要があることです。
新しいテストケースを追加するときは、`describe` 、 `context` 、 `subject` で作られたネスト構造を壊さないように追加するか、全く別のネスト構造を作る必要があります。
(汚い手ですが、既存のネスト構造を無視して書く方法もあります...)

私は上記のような事態に疲れました。
`it` 句が `subject` や `context` に依存しているために気軽にテストを追加できないのです。
そのため、なるべく `it` 句を読めばわかるようなテストを書くことにしました。
(`subject` を使わない場合 `it` は不自然なので `example` を使うことにしました)

`describe` や `context` によるネスト構造もなるべく使わないように心がけています。
`if` 文やループ文でネストが深くなるとコードが読みづらくなることはよく知られていることです。
私は `describe` や `context` のネストも複雑になりがちであると思います。

`let` や `before` も `it` 句から離れると、 `it` 句が実行される時の状態が読みづらくなります。
`describe` や `context` のネストの中に `let` や `before` が織り交ぜられると余計にわかりづらくなります。
極力 `it` 句だけで理解できるように心がけますが、 `it` 句だけでは DRY にかけないことがあるので、そのような場合だけ `let` や `before` を使うことにしています。

`it` 句に集中することでテストをシンプルにします。

ただ、この方法にはデメリットもあります。

### シンプルさのために諦めたこと

ケースBの方法は `it` 句に多くのものを詰め込むことで、 `it` 句だけ読めば理解できるテストを目指します。
これはシンプルなテストを作ることができますが、一方次のようなことは諦めました。

- `format doc` の出力結果を綺麗にすること
- 単一条件テスト (1つの `it` の中で 1つの条件だけテストすること)

## Power Assert について

ケースBでは `is_asserted_by` という matcher を使っています。
見慣れない方が多いと思いますが、これは [joker1007/rspec-power_assert](https://github.com/joker1007/rspec-power_assert) gem をいれることで使えるようになる PowerAssert の matcher です。

僕は Power Assert が好きで、例外と配列の比較以外は Power Assert でいいかなと思っています。
須藤さんに怒られそうですが... (参考: https://www.youtube.com/watch?v=g33d9SSbvd8)

僕としては Power Assert を使うことで matcher について調べる時間を短縮できる事がとても助かっています。
毎回ほぼ `is_asserted_by` だけでいいし、 `is_asserted_by` のブロック内の書き方は Ruby の文法で済むので調べる必要はありません。
Power Assert が失敗した時のメッセージが分かりづらいケースがあるというのは、そのとおりですが、エラーメッセージを読むときはテストコードも一緒に読むし、だいたいの場合理解できるのではないかと楽観的に考えています。

基本的にテストが落ちるのはTDDを行っている時だと思うので、テストがどのように落ちるかもある程度予想しながら書いていると思います。
その場合、多少エラーメッセージが読みづらくてもいいかなと思います。わりと十分わかりますし。。。

後からテストコードを読む時も、`is_asserted_by` のブロック内は簡単な Ruby のコードなので読めると思います。

## 参考

### 影響を受けたもの

- Go 言語のテストの書き方
- [trailblazer](https://leanpub.com/trailblazer)

念の為タイトルの元ネタ: [俺の妹がこんなに可愛いわけがない](https://ja.wikipedia.org/wiki/%E4%BF%BA%E3%81%AE%E5%A6%B9%E3%81%8C%E3%81%93%E3%82%93%E3%81%AA%E3%81%AB%E5%8F%AF%E6%84%9B%E3%81%84%E3%82%8F%E3%81%91%E3%81%8C%E3%81%AA%E3%81%84)