🐘

RSpec の実行順序と AAA パターンについて

2024/05/21に公開

概要

単体テストの考え方/使い方という本で、
AAA パターンというものを学びました。

今回は RSpec の実行順序を、この AAA パターンに絡めて解説していきます。

AAA バターンとは

単体テストの構造ののことで、以下の3つで構成されている。

  • Arrange(準備)
  • Act(実行)
  • Assert(確認)

RSpec の例題

RSpec では、describe, context, let, let!, before準備に相当し、
it実行expect(RSpec it 句が通ればグリーンになる) が 確認に当たります。

これらは準備→実行→確認の順になっています。

準備フェーズ

  1. すべての describe 句とその中の context 句が実行
  2. それぞれの it 句の前に定義される let!before が実行

実行フェーズ
it 句内が実行

確認フェーズ
it 句内の expect 句が実行

以下のような RSpec で見てみましょう。

sample_spec.rb
require 'rails_helper'

RSpec.describe "Sample Spec" do
  let(:num0) do
    pp "let num0"
  end

  let!(:num0_2) do
    pp "let! num0_2"
  end

  describe "describe1" do
    pp "describe1"

    let(:num1) do
      pp "let num1"
    end

    before "before1" do
      pp "before1"
    end

    context "context1" do
      pp "context1"

      let!(:num2) do
        pp "let! num2"
      end

      before "before2" do
        pp "before2"
      end

      it "it1" do
        pp "it1"
      end
    end

    context "context2" do
      pp "context2"

      let!(:num3) do
        pp "let! num3"
      end

      before "before3" do
        pp "before3"
      end

      it "it2" do
        pp "it2"
      end
    end
  end

  describe "describe2" do
    pp "describe2"

    context "context3" do
      pp "context3"

      before "before4" do
        pp "before4"
      end

      let!(:num4) do
        pp "let! num4"
      end

      it "it3" do
        pp "it3"
      end
    end
  end
end

これを実行すると、以下のような出力がされます。
各々の実行タイミングはコメントの通りです。

> bundle exec rspec spec/models/sample_spec.rb
"describe1"    # describe1 が実行.......................(準備フェーズ)
"context1"     # describe1 内の context1 が実行..........(準備フェーズ)
"context2"     # describe1 内の context2 が実行..........(準備フェーズ)
"describe2"    # describe2 が実行.......................(準備フェーズ)
"context3"     # describe2 内の context3 が実行..........(準備フェーズ)
"let! num0_2"  # context1 内の it1 前の let! が実行.......(準備フェーズ)
"before1"      # context1 内の it1 前の before1 が実行....(準備フェーズ)
"let! num2"    # context1 内の it1 前の let! が実行.......(準備フェーズ)
"before2"      # context1 内の it1 前の before2 が実行....(準備フェーズ)
"it1"          # context1 内の it1 が実行................(実行フェーズ)
.              # it1 内が成功........................... (確認フェーズ)
"let! num0_2"  # context2 内の it2 前の let! が実行...... (準備フェーズ)
"before1"      # context2 内の it2 前の before1 が実行... (準備フェーズ)
"let! num3"    # context2 内の it2 前の let! が実行...... (準備フェーズ)
"before3"      # context2 内の it2 前の before3 が実行....(準備フェーズ)
"it2"          # context2 内の it2 が実行................(実行フェーズ)
.              # it2 内が成功........................... (確認フェーズ)
"let! num0_2"  # context3 it3 前の let! が実行...........(準備フェーズ)
"before4"      # context3 it3 の前の before4 が実行.......(準備フェーズ)
"let! num4"    # context3 it3 の前の let! が実行..........(準備フェーズ)
"it3"          # context3 内の it3 が実行................ (実行フェーズ)
.              # it3 内が成功............................(確認フェーズ)

まとめ

結果を見てみると、RSpec では、まずすべての describe 句と context 句が実行され、
テストの構造を定義しているようです。(準備)

次に 各 it 句を実行する前に、before, let!が実行され、確認で使用する値を定義しています。(準備)

上記の準備が終わると、it 句が実行されています。(実行)

最後に各 it 句内が期待通りになっているか検証されています(確認)

また、let に関しては遅延評価で、コード上ではどこからも呼ばれていないため、出力されていません。

Discussion