rspecのrspecに学ぶ、シンプルなrspecの書き方

## 無駄に複雑なrspec
rspecで書いたテストケースは、contextのネストが深くなることがある。
BDDのテストケースは下記の形式をとるので、これをcontextで表現しようとすると難しいことがある。

```
Given：最初の文脈（前提）があって、
When：イベントが発生した場合、
Then：なんらかのアウトプットを保証する。
```

例えば、fizz_buzz問題でfizzを出力する振る舞いを確認する場合はこうなる。事前条件は必要ないので書いていない。シンプルで、contextとitをつなげてみたときにわかりやすい英文として記述できる。

```ruby
describe "#fizz_buzz" do
  subject { fizz_buzz(input) }
  context "when input is multiple of 3" do
    let!(:input) { 3 }
    it { is_expected to eq "fizz" }
  end
end
```

ここで、3の倍数に対する入力がfizzであることを確認するのに、3だけを確認するのは心細いことに気づく。

上述のフォーマットを崩さないように、itの中身を変えないことを意識するとこうなる。

```ruby
describe "#fizz_buzz" do
  subject { fizz_buzz(input) }
  context "when input is multiple of 3" do
    let!(:input) { 3 }
    it { is_expected to eq "fizz" }
  end
  context "when input is multiple of 3" do
    let!(:input) { 12 }
    it { is_expected to eq "fizz" }
  end
  context "when input is multiple of 3" do
    let!(:input) { 303 }
    it { is_expected to eq "fizz" }
  end
end
```

同じcontextが横並びしてしまった。5の倍数や3と5の倍数のパターンも記述することを考えると、3の倍数のパターンはひとくくりにしたほうがわかりやすそうだ。

```ruby
describe "#fizz_buzz" do
  subject { fizz_buzz(input) }
  context "when input is multiple of 3" do
    context "inputting 3" do
      let!(:input) { 3 }
      it { is_expected to eq "fizz" }
    end
    context "inputting 12" do
      let!(:input) { 12 }
      it { is_expected to eq "fizz" }
    end
    context "inputting 303" do
      let!(:input) { 303 }
      it { is_expected to eq "fizz" }
    end
  end
end
```
ネストが深くなってしまった。`context "inputting 3" do`というのは、letで与えている数字と重複しているし、そもそも3を選んだことにはあまり意味はない。3の倍数であれば何でも良かったのだが、contextにまで顔を出してしまっている。

愚直にこうしてはいけないのだろうか？`is_expected`のようなおしゃれな構文を手放した代わりに、ネストが浅く、単純で理解しやすいexpectが並んでいる。

```ruby
describe "#fizz_buzz" do
  context "when input is multiple of 3" do
    it "returns fizz" do
      expected = "fizz"
      expect(fizz_buzz(3)).to eq expected
      expect(fizz_buzz(12)).to eq expected
      expect(fizz_buzz(303)).to eq expected
    end
  end
end
```

## rspecの書き方に関する議論
どうも、Qiitaなどで見かける記事では、subjectを使おうとか、contextを細かく分けていたり、またそのcontextの中にちょっとずつbeforeが入っていてなんかしている事が多い。[要出典]

私は異端者なのだろうか。
なんか腑に落ちないので、rspecのrspecを読んでみた。

## rspecのrspecの流儀
rspec-coreのrspecを読んだところ、指針らしきものが見えてきた。結論から言うと、rspecのrspecではとても愚直にrspecを記述している。

https://github.com/rspec/rspec-core/tree/main/spec

### contextにbefore、letが無くてもいい
- contextのbeforeやletは無理して使わない。
- itの中で事前条件のためのセットアップをすることが多い

```ruby
    context "when RSpec.configuration.format_docstrings is set to a block" do
      it "formats the description using the block" do
        RSpec.configuration.format_docstrings { |s| s.upcase }
        example_group.example { }
        example_group.run
        pattern = /EXAMPLE AT #{relative_path(__FILE__).upcase}:#{__LINE__ - 2}/
        expect(example_group.examples.first.description).to match(pattern)
      end
    end
```

[ソース](https://github.com/rspec/rspec-core/blob/main/spec/rspec/core/example_spec.rb#L174-L182)

### itの中身は一行でなくてもいい
- 事前条件のセットアップを含めて、そこそこ長い記述をすることもある
- ただし、これが許されるのはあくまで1つの振る舞いを確認している場合。複数の振る舞いを1つのitで確認することは無い

```ruby
    it "clears examples, failed_examples and pending_examples" do
      reporter.start(3)
      pending_ex = failing_ex = nil
      # （中略）事前条件用のセットアップ処理が続く

      RSpec.clear_examples

      reporter.register_listener(listener, :dump_summary)

      expect(listener).to receive(:dump_summary) do |notification|
        expect(notification.examples).to be_empty
        expect(notification.failed_examples).to be_empty
        expect(notification.pending_examples).to be_empty
      end

      reporter.start(0)
      reporter.finish
    end
```

[ソース](https://github.com/rspec/rspec-core/blob/04d5e5264daf51bac0e40573381eaea356737883/spec/rspec/core_spec.rb#L180-L208)

### itの中身に事前条件のセットアップを含んでもいい
- というかほとんどそうしている

### is_expectedは無理に使わない
- ほぼ見かけなかった

### itに複数のexpectを書いていい
- 1つの振る舞いに関することであれば1つのitに複数のexpectを書く
- 1つの振る舞いを観測するために複数の検証が必要なことは普通にある

```ruby
    it 'returns the absolute location of the exe/rspec file' do
      expect(File.exist? RSpec::Core.path_to_executable).to be(true)
      expect(File.read(RSpec::Core.path_to_executable)).to include("RSpec::Core::Runner.invoke")
      expect(File.executable? RSpec::Core.path_to_executable).to be(true) unless RSpec::Support::OS.windows?
    end
```

[ソースコード](https://github.com/rspec/rspec-core/blob/04d5e5264daf51bac0e40573381eaea356737883/spec/rspec/core_spec.rb#L291-L295)

### itは振る舞いで分割する
- expectを複数書くのを許容しているが、何でもかんでも書いているわけではない。
- 外から見た動きとして特徴づけられることごとにitを分ける

```ruby
    it "clears example groups" do
      RSpec.world.example_groups << :example_group

      RSpec.clear_examples

      expect(RSpec.world.example_groups).to be_empty
    end

    it "resets start_time" do
      start_time_before_clear = RSpec.configuration.start_time

      RSpec.clear_examples

      expect(RSpec.configuration.start_time).not_to eq(start_time_before_clear)
    end
```

[ソースコード](https://github.com/rspec/rspec-core/blob/04d5e5264daf51bac0e40573381eaea356737883/spec/rspec/core_spec.rb#L164-L178)

### describeやcontext内でメソッド定義してitの中身を短くする
- subjectやletは無理に使わず普通にメソッド定義する
- ちなみに、describe, context内でのメソッド定義はcontextローカルなヘルパーとして登録される。

```ruby
RSpec.describe 'command line', :ui do
#（中略）
  def examples(group)
    yield split_in_half(stdout.string.scan(/^\s+#{group} example.*$/))
  end
```

[ソースコード](https://github.com/rspec/rspec-core/blob/04d5e5264daf51bac0e40573381eaea356737883/spec/integration/order_spec.rb#L195-L197)

## rspecに学ぶrspecの書き方のミソ
どうやら、rspecを書く上で重要なのは下記二点のよう。subjectとかis_expectedとかはこれらを満たした上で使えたら使おう。

- 1つの振る舞いにつき1つのit（振る舞いの単位は観測者によって変わる）
- contextとitに書いてあることが英文として読みやすいように構成する

これらの考え方に沿うと、冒頭で紹介した`is_expected`を使わないrspecの記述は許容されて良いものであるということがわかる。

### 関連記事
今回見出したテスト記述の指針は、下記の記事で紹介されている「雑な書き方」によく似ています。

https://qiita.com/jnchito/items/a90b3b09d008227d3d60

https://qiita.com/kbaba1001/items/5333d325d0a76dd29581