概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: RSpec - tell a meaningful story with your tests
- 原文公開日: 2018/02/19
- 著者: Paweł Dąbrowsk
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. 検証
最後の部分では、すべてうまく動いたかどうかをテストしなければなりません。正しい結果が返ったか、正しいクラスが呼び出されたか。
ただしここで忘れてはならないことがあります。上のような戦略は、テストを読みやすくメンテ可能にするためにlet
やbefore
ブロックを大量に使うことを強いられる大規模なテストには合いません。
お知らせ: RSpec & TDDの電子書籍を無料でダウンロード
もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。