アルパカログ

Webエンジニア兼マネージャーがプログラミングやマネジメント、読んだ本のまとめを中心に書いてます。

RSpec letとcontextを使ってテストの見通しを良くする方法解説

f:id:otoyo0122:20201019092631p:plain:w300

RSpecを始めたばかりの人にとって、letやcontextは最初は少し難しく思えるかもしれません。

しかし、letやcontextを使いこなすことでテストの見通しを格段に良くすることができます。

このエントリでは、RSpecのletとcontextの使ってテストの見通しを良くする方法を解説します。

まずは普通にテストを書いてみる

今回テストするのは次のように与えられた引数を加算するaddメソッドです。

1点注意しなければならないのは、引数a, bのどちらかが整数値でなければnilを返す点です。

class Calculator
  def add(a, b)
    return unless a.is_a?(Integer) && b.is_a?(Integer)
    a + b
  end
end

実行結果も見ておきましょう。実行結果は次のようになります。

calc = Calculator.new

puts "a: 1,   b: 2   => #{calc.add(1, 2)}"   # a: 1,   b: 2   => 3
puts "a: nil, b: 2   => #{calc.add(nil, 2)}" # a: nil, b: 2   => 
puts "a: 1,   b: nil => #{calc.add(1, nil)}" # a: 1,   b: nil => 

addメソッドのテストを、最初はletやcontextを使わないで書いてみます。おそらく次のようになるでしょう。

RSpec.describe "Calculator" do
  describe "#add" do
    it "returns the sum" do
      a = 1
      b = 2
      calc = Calculator.new
      expect(calc.add(a, b)).to eq(a + b)
    end
  end
end

しかし、これではabが整数値でない場合のテストが足りません。そこでcontextの出番です。

「〜のとき」をcontextで表す

まず、contextによってテストのコンテキストを明示してみます。

abが整数値であるときを表すためにcontext "when both a and b are integer"を追加します。

RSpec.describe "Calculator" do
  describe "#add" do
    context "when both a and b are integer" do
      it "returns the sum" do
        a = 1
        b = 2
        calc = Calculator.new
        expect(calc.add(a, b)).to eq(a + b)
      end
    end
  end
end

これによって「abの両方が整数値のとき」が明確になりました。

同じようにして「aが整数値でないとき」と「bが整数値でないとき」のコンテキストを追加します。

RSpec.describe "Calculator" do
  describe "#add" do
    context "when both a and b are integer" do
      ...
    end

    context "when a is not an integer" do
      it "returns nil" do
        a = nil
        b = 2
        calc = Calculator.new
        expect(calc.add(a, b)).to ne_nil
      end
    end

    context "when b is not an integer" do
      it "returns nil" do
        a = 1
        b = nil
        calc = Calculator.new
        expect(calc.add(a, b)).to be_nil
      end
    end
  end
end

contextitを読むだけで、どんなときに何が返ってくるかがわかりやすいテストになりました。

しかし、contextごとに変わる引数がどうなっているかは、itの中身を見なければわかりません。

そこでletを使ってさらに見通しを良くします。

letを使って変数の見通しを良くする

contextを使って「abが整数値のとき」や「aが整数値でないとき」を表すことができました。

しかし、本当にabがそうなっているか、実際に何が入っているかはitの中身を見なければわかりませんでした。

そこでletを使ってabを定義してあげることで見通しを良くします。

RSpec.describe "Calculator" do
  describe "#add" do
    context "when both a and b are integer" do
      let(:a) { 1 }
      let(:b) { 2 }

      it "returns the sum" do
        ...
      end
    end

    context "when a is not an integer" do
      let(:a) { nil }
      let(:b) { 2 }

      it "returns nil" do
        ...
      end
    end

    context "when b is not an integer" do
      let(:a) { 1 }
      let(:b) { nil }

      it "returns nil" do
        ...
      end
    end
  end
end

abcontextの近くに移動したことで、コンテキストごとの変数の定義が明確になりました。

さらに、上位のブロックで共通のletを定義しておくことでもっとシンプルにできます。

下記が完成の形です。

RSpec.describe "Calculator" do
  describe "#add" do
    let(:a) { 1 }
    let(:b) { 2 }

    context "when both a and b are integer" do
      it "returns the sum" do
        calc = Calculator.new
        expect(calc.add(a, b)).to eq(a + b)
      end
    end

    context "when a is not an integer" do
      let(:a) { nil }

      it "returns nil" do
        calc = Calculator.new
        expect(calc.add(a, b)).to ne_nil
      end
    end

    context "when b is not an integer" do
      let(:b) { nil }

      it "returns nil" do
        calc = Calculator.new
        expect(calc.add(a, b)).to be_nil
      end
    end
  end
end

ここではletを単純な変数の定義にしか使っていませんが、letにはブロックの評価をメモ化するという有用な役割があります。

letのメリットについて詳しくは下記の記事を参照してみてください。

以上です。

このエントリでは、RSpecのletとcontextの使ってテストの見通しを良くする方法を解説しました。