LoginSignup
1
0

RSpecでテストケースのテストを書きたい

Last updated at Posted at 2023-12-01

SmartHR Advent Calendar 2023 シリーズ2の1日目です。

最近、あるメソッドに対してテストケースが漏れなく書かれていることを保証するテストがほしいと思うことがあったので調べてみた記録を残します。

どういうときにそのようなテストが欲しくなるか、具体例を見ながら説明していきます。

具体例

ここに2つの数値と演算子となる文字列を渡すと計算結果を返してくれる.calcメソッドを持つCalculatorクラスがあります。
現時点では足し算(plus)、引き算(minus)に対応しています。

class Calculator
  AVAILABLE_OPERATORS = %w(plus minus)# 足し算、引き算のみできる

  def self.calc(left, operator, right)
    raise ArgumentError unless AVAILABLE_OPERATORS.include?(operator)

    case operator
    when 'plus'
      left + right
    when 'minus'
      left - right
    end
  end
end

このメソッドに対するテストを以下のように書いたとしましょう。

RSpec.describe Calculator do
  describe ".calc" do
    context 'plus' do
      it { expect(Calculator.calc(1, 'plus', 2)).to eq(3) }
    end

    context 'minus' do
      it { expect(Calculator.calc(3, 'minus', 2)).to eq(1) }
    end

    context 'invalid operator' do
      it { expect { Calculator.calc(1, 'invalid', 2) }.to raise_error(ArgumentError) }
    end
  end
end

すべての処理を通っておりカバレッジは100%です。よかったですね。

その後、追加で掛け算、割り算にも対応することになったので実装しました。

class Calculator
  AVAILABLE_OPERATORS = %w(plus minus times divide) # 掛け算、割り算もできるようにした

  def self.calc(left, operator, right)
    raise ArgumentError unless AVAILABLE_OPERATORS.include?(operator)

    case operator
    when 'plus'
      left + right
    when 'minus'
      left - right
    when 'times'
      left * right
    when 'divide'
      left / right
    end
  end
end

無事、掛け算や割り算を追加できちゃんと動くことも確認できました。おめでとうございます。

本題

しかし、ここからが本題。この実装をした時点でテストの追加を忘れていた場合でもテストはパスしてしまいます。コードレビューでも見逃してしまった場合、テストがない状態で本番にリリースされてしまいます。

また今回はケースが4つだけなので仮にすべてのテストケースが書いてあったときには書かれている!とわかりますが、ケースが数十にもなってくる場合、全てのテストケースを網羅できているかどうか目で見て判断することは難しくなってくると思います。

この抜け漏れの防止を仕組みで解決したいというのがモチベーションでした。

どうすればいいのか

調べたりChatGPTとお話したりCopilotに導かれるまま指を動かしてみたりしました。

するとRSpecにはメタデータを取得するメソッドが用意されており、それを使うことでやりたいことを実現できそうでした。

RSpec.describe Calculator do
  describe ".calc" do
    it 'すべての演算子のテストが存在すること' do
      # テストのタイトルを取得する
      example_descriptions = RSpec.current_example.example_group.children.map(&:description)
      # => ["plus", "minus", "invalid operator"]
 
      Calculator::AVAILABLE_OPERATORS.each do |operator|
        expect(example_descriptions).to include(operator)
        # => times、devideのテストがないのでfailedとなる
      end
    end

    context 'plus' do
      it { expect(Calculator.calc(1, 'plus', 2)).to eq(3) }
    end

    context 'minus' do
      it { expect(Calculator.calc(3, 'minus', 2)).to eq(1) }
    end

    context 'invalid operator' do
      it { expect { Calculator.calc(1, 'invalid', 2) }.to raise_error(ArgumentError) }
    end
  end
end

これで、演算子を追加したらテストの追加も強制することができるようになりました。

まとめ

他にもカバレッジをきちんと計測する方法など解決手段はいくつかあると思います。テストケースのテストをすることの是非についてはまだあまり考えられていないですが、少なくともこういったケースでは有効なこともあるんじゃないかと思っています。

今回の調査でRSpecのコードも少し読んだのですが、こんなメソッドもあるんだ便利〜とよい勉強にもなりました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0