Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@kuboon

カバレッジ100%を目指す

オレオレルールを晒す第二弾。

ソースコード内のどの程度の割合を自動テスト出来るか、という指標を、テストカバレッジといいます。カバレッジの定義を真面目にやろうとするとC0とかC1とかいろいろ出てきますが、ここでは「とりあえず一通り動かす」という雑な理解で話を進めます。

ruby の弱点

よく言われる ruby の弱点の1つとして、「動的言語なので、他の言語ではコンパイル時にわかるエラーが事前に発見できない」というものがあります。
例えば以下のコードを御覧ください。

def edit
  @post = Post.find(params[:id])
  raise RestfulError::Forbiden unless @post.user_id == current_user.id
end

RSpec.describe 'edit' do
  subject { get :edit, id: post.id, session }
  let(:post){ FactoryGirl.create(:post, user_id: user.id) }
  it { is_exptected.to render_template('edit') }
end

編集しようとする post が自分の post じゃなければ例外を投げる、という処理です。テストを雑に用意しましたが、このテストは raise RestfulError::Forbiden の部分を通りません。
実は Forbiden の部分でスペルミスをしているのですが、 ruby は動的言語ゆえ、実際にスペルミスの箇所を実行しない限り、エラーが起きません。 Java や C のようなコンパイル畑からやってきた方には「これだからrubyちゃんは信用できないのよね」と陰口を叩かれがちです。

そこで。
自動テストで「とりあえず全てのコード片を一通り評価させる」というのを目指すわけです。

(ちなみに RestfulError は拙作の gem です。ぜひご利用ください!)

条件分岐にそって context を書く

状況によって挙動が変わるメソッドのテストを書くときには、取りうる値を全部テストせよとか境界条件をテストせよとかこれもいろいろ理論があるのですが、とりあえず置いといて、ここではとにかく「カバレッジ100%を目指す」ことだけを考えます。

上述のコードを、条件分岐にそって context を書くと以下のようになります。

RSpec.describe 'edit' do
  subject { get :edit, id: post.id, session }
  let(:post){ FactoryGirl.create(:post, user_id: post_user_id) }
  context '@post.user_id == current_user.id'
    let(:post_user_id){ user.id }
    it { is_exptected.to render_template('edit') }
  end
  context '@post.user_id != current_user.id'
    let(:post_user_id){ 0 }
    it { exptect{subject}.to raise_error }
  end
end

(describe の次行に subject を、 context の次行に let を書く も参照ください)

もう少し複雑に条件分岐している例はこちら。条件分岐にそって context を書くようにすると、構造化のルールが一定し、100%カバーテストを記述しやすくなります。

def fizzbuzz(num)
  if num % 3 == 0
    ret = 'Fizz'
    if num % 5 == 0
      ret << 'Buzz'
    end
  elsif num % 5 == 0
    ret = 'Buzz'
  else
    ret = num.to_s
  end
  ret
end

RSpec.describe 'fizzbuzz' do
  subject { fizzbuzz(num) }
  context 'num % 3 == 0' do
    let(:num){ 3 }
    it { is_expected.to eq 'Fizz' }
    context '&& num % 5 == 0' do
      let(:num){ 15 }
      it { is_expected.to eq 'FizzBuzz' }
    end
  end
  context 'num % 3 != 0 && num % 5 == 0' do
    let(:num){ 5 }
    it { is_expected.to eq 'Buzz' }
  end
  context 'num % 3 != 0 && num % 5 != 0' do
    let(:num){ 4 }
    it { is_expected.to eq '4' }
  end
end
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What are the problem?