Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails tips: ストーリーのあるRSpecテストを書く(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails tips: ストーリーのあるRSpecテストを書く(翻訳)

これまでにRSpecのspyスタブについて学んできましたので、今回はもっと意味のあるspecを作ってみましょう。テストというと、「どう動いているか」をテストすると考えているかもしれません。しかしそのテストを集めてドキュメントをビルドするときのことについて考えていますか?別に余分なことをする必要はありませんが、しなければならないのは「ストーリーが見えるテストを書く」ことだけです。その方法についてご説明いたします。

ストーリーの骨格

よいストーリーにはしっかりした構造があり、だからこそ簡単に追うことができます。ストーリーを追うためにあちこち彷徨わなければならないとしたら、流れを見失ってしまうかもしれません。あなたがテストを読むときにまさにこれがしばしば起きているのです。

設計のまずいテストをちょっと見てみましょう。

require 'spec_helper'

describe NameService do
  let(:name_service) { described_class.new(user) }
  let(:user) { instance_double(User, full_name: 'Mike Willson') }
  let(:admin_policy) { instance_double(AdminPolicy, admin?: false) }

  before do
    allow(AdminPolicy).to receive(:new).with(user).and_return(admin_policy)
  end

  describe '#full_name' do
    subject { name_service.full_name }

    it 'ユーザーのフルネームを返す' do
      expect(subject).to eq(user.full_name)
    end

    context 'ユーザーがadminの場合' do
      before { allow(admin_policy).to receive(:admin?).and_return(true) }

      it 'フルネームの前に`ADMIN`を付けて返す' do
        expect(subject).to eq("ADMIN #{user.full_name}")
      end
    end
  end
end

ここからどんなストーリーが見えてきますか?下手な台本ですね。最初にPolicy Objectを宣言していますが、それがどう使われているのかがわかりません。こんな台本の導入部など読んでられないでしょうから、itタグに進みましょう。

itはたった1行の短いストーリーです。しかしクラスのコードを読んでおかなければ、しばらくの間目を凝らさないと何のメソッドをテストしているのかわかりません(クラスのコードは非常に短いのですが、ここではあえて掲載しませんでした)。

それではもっとましなストーリーを書いてみましょう。

require 'spec_helper'

describe NameService do
  describe '#full_name' do
    it 'ユーザーのフルネームを返す' do
      user = instance_double(User, full_name: 'Mike Willson')
      admin_policy = instance_double(AdminPolicy, admin?: false)
      allow(AdminPolicy).to receive(:new).with(user).and_return(admin_policy)

      name_service = NameService.new(user)
      full_name = user.full_name

      expect(admin_policy).to have_received(:admin?).once
      expect(full_name).to eq(user.full_name)
    end

    it 'ユーザーがadminの場合フルネームの前に`ADMIN`を付けて返す' do
      user = instance_double(User, full_name: 'Mike Willson')
      admin_policy = instance_double(AdminPolicy, admin?: true)
      allow(AdminPolicy).to receive(:new).with(user).and_return(admin_policy)

      name_service = NameService.new(user)
      full_name = user.full_name

      expect(admin_policy).to have_received(:admin?).once
      expect(full_name).to eq("ADMIN #{user.full_name}")
    end
  end
end

今度はずっと読みやすくてよいストーリーだと思いませんか?導入部を除くと2つの章があり(共通のコードを別メソッドに切り出してDRYにすることもできます)。あっちこっち飛び回らずに上から下まですっと読み下すことができ、しかもそれぞれのストーリーがちゃんと独立しています。

よいストーリーを書くには、よい作文スタイルを守らなければなりません。上の例の場合、それぞれのストーリーがセットアップ、操作、検証の3部構成になっています。

1. セットアップ

このサービスで使うクラスをスタブ化し、外部コードを呼び出したりしないようオブジェクトを準備します。テスト対象のクラスで使われるクラスやオブジェクトがどんな値を返すかもわかります。セットアップでは、スタブ化とコードの準備を行います。

2. 操作

ここは最もシンプルな部分です。クラスのインスタンスを作成して、テストしたいコードを呼ぶだけです。

3. 検証

最後の部分では、すべてうまく動いたかどうかをテストしなければなりません。正しい結果が返ったか、正しいクラスが呼び出されたか。

ただしここで忘れてはならないことがあります。上のような戦略は、テストを読みやすくメンテ可能にするためにletbeforeブロックを大量に使うことを強いられる大規模なテストには合いません。

お知らせ: RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

Ruby: テストを不安定にする5つの残念な書き方(翻訳)

Railsのテスティングピラミッド(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。