どこでも見れるメモ帳

とあるSEの備忘録。何かあれば気軽にコメントください〜

RSpecによるテスト記述

f:id:ni66ling:20160611144301p:plain

はじめに

RSpec2の書き方について,考えがまとまってきたので残しておきます.
テスト作成における全体流れとしては,

  1. メソッドレベルでテストケースを作成
  2. 1.をRSpecに落としこむ

ただし,1.におけるテストケースは2.に向けて書きます.

1. メソッドレベルでテストケースを作成

具体的には,下のようなテストケース表を作ります.

#ケース テストケース名 入力1:input_1 出力 例外
1 正常系:〜のとき,〜を返却すること      
2 異常系:〜のとき,〜の例外を投げること     例外名, 例外メッセージ

具体例を考えてみます.
例えば,以下のようなクラスに対するテストケースを考えてみます.

# -*- mode:ruby; coding:utf-8 -*-
class ClassName
  def self.method_name(input_1, input_2)
    return input_1 + input_2
  end
end
#ケース テストケース名 入力1:input_1 入力2:input_2 出力 例外
1 正常系:入力値がともに文字列であるとき,それらを連結した文字列を返却すること 'hoge' 'fuga' 'hogefuga'  
2 異常系:input_1が文字列以外であるとき,例外を投げること nil 'fuga' ArgumentError, '入力値が不正です.input_1が文字列以外です.'
3 異常系:input_2が文字列以外であるとき,例外を投げること 'hoge' 3 ArgumentError, '入力値が不正です.input_2が文字列以外です.'

テストケースの作成方法については,ここでは要因分析*1に基いて考えています.

2. テストケースをRSpecに落としこむ

1.で作成したテストケースを,以下のフォーマットに埋め込む形でテストコードを作成します.

# -*- mode:ruby; coding:utf-8 -*-
require './test.rb' # テスト対象のファイルの相対パス
describe ClassName do
  describe "method_name" do
    context '正常系' do
      it '〜のとき,〜を返却すること' do
        # 入力引数
        input = 入力値
        # 出力
        result = 出力値
        # テスト
        expect( ClassName.method_name(input) ).to eq(result)
      end
    end

    context '異常系' do
      it '〜のとき,〜の例外を投げること' do
        # 入力引数
        input = 入力値
        # テスト
        expect{ ClassName.method_name(input) }.to raise_error(例外名, '例外メッセージ')
      end
    end
  end
end

1.で考えた具体例について,RSpecを作成してみます.
これは,ほぼコピペ作業で済みます.

# -*- mode:ruby; coding:utf-8 -*-
require './test.rb' # テスト対象のファイルの相対パス
describe ClassName do
  describe ".method_name" do
    context "正常系" do
      it '入力値がともに文字列であるとき,それらを連結した文字列を返却すること' do
        # 入力引数
        input_1 = 'hoge'
        input_2 = 'fuga'
        # 出力
        result = 'hogefuga'
        # テスト
        expect( ClassName.method_name(input_1, input_2) ).to eq(result)
      end
    end

    context '異常系' do
      it 'input_1が文字列以外であるとき,例外を投げること' do
        # 入力引数
        input_1 = nil
        input_2 = 'fuga'
        # テスト
        expect{ ClassName.method_name(input_1, input_2) }.to raise_error(ArgumentError, '入力値が不正です.input_1が文字列以外です.')
      end

      it 'input_2が文字列以外であるとき,例外を投げること' do
        # 入力引数
        input_1 = 'hoge'
        input_2 = 3
        # テスト
        expect{ ClassName.method_name(input_1, input_2) }.to raise_error(ArgumentError, '入力値が不正です.input_2が文字列以外です.')
      end
    end
  end
end

今の状態(例外処理を加えていない状態)でrspecを実行すると次のようになります.

% rspec test_spec.rb -fs                                                                                                                [~/ruby/rspec]

ClassName
  .method_name
    正常系
      入力値がともに文字列であるとき,それらを連結した文字列を返却すること
    異常系
      input_1が文字列以外であるとき,例外を投げること (FAILED - 1)
      input_2が文字列以外であるとき,例外を投げること (FAILED - 2)

Failures:

  1) ClassName.method_name 異常系 input_1が文字列以外であるとき,例外を投げること
     Failure/Error: expect{ ClassName.method_name(input_1, input_2) }.to raise_error(ArgumentError, '入力値が不正です.input_1が文字列以外です.')
       expected ArgumentError with "入力値が不正です.input_1が文字列以外です.", got #<NoMethodError: undefined method `+' for nil:NilClass> with backtrace:
         # ./test.rb:7:in `method_name'
         # ./test_spec.rb:25:in `block (5 levels) in <top (required)>'
         # ./test_spec.rb:25:in `block (4 levels) in <top (required)>'
     # ./test_spec.rb:25:in `block (4 levels) in <top (required)>'

  2) ClassName.method_name 異常系 input_2が文字列以外であるとき,例外を投げること
     Failure/Error: expect{ ClassName.method_name(input_1, input_2) }.to raise_error(ArgumentError, '入力値が不正です.input_2が文字列以外です.')
       expected ArgumentError with "入力値が不正です.input_2が文字列以外です.", got #<TypeError: can't convert Fixnum into String> with backtrace:
         # ./test.rb:7:in `+'
         # ./test.rb:7:in `method_name'
         # ./test_spec.rb:33:in `block (5 levels) in <top (required)>'
         # ./test_spec.rb:33:in `block (4 levels) in <top (required)>'
     # ./test_spec.rb:33:in `block (4 levels) in <top (required)>'

Finished in 0.00495 seconds
3 examples, 2 failures

Failed examples:

rspec ./test_spec.rb:20 # ClassName.method_name 異常系 input_1が文字列以外であるとき,例外を投げること
rspec ./test_spec.rb:28 # ClassName.method_name 異常系 input_2が文字列以外であるとき,例外を投げること

では,RSpecを満たすように対象クラスを作成します.

# -*- mode:ruby; coding:utf-8 -*-
class ClassName
  def self.method_name(input_1, input_2)
    raise(ArgumentError, '入力値が不正です.input_1が文字列以外です.') if input_1.class != String
    raise(ArgumentError, '入力値が不正です.input_2が文字列以外です.') if input_2.class != String
    return input_1 + input_2
  end
end

これでRSpecの結果がオールグリーンになりました.

% rspec test_spec.rb -fs                                                                                                                [~/ruby/rspec]

ClassName
  method_name
    正常系
      入力値がともに文字列であるとき,それらを連結した文字列を返却すること
    異常系
      input_1が文字列以外であるとき,例外を投げること
      input_2が文字列以外であるとき,例外を投げること

Finished in 0.00257 seconds
3 examples, 0 failures

おしまい^^

*1:入力引数それぞれについて,規定の型であるかどうか等