RSpecにfakeデータ生成ツールを導入する話
こんにちは、アプリケーションエンジニアの菅野です。
入社以来リモートワークをエンジョイしていますが、通勤時間がなくなったことにより本を読む時間がめっきり減ってきたのが最近の悩みです。
これは?
このエントリではテストにおけるfakeデータの説明や背景を紹介した上で、RSpecでfakeデータ生成gemを導入・利用することを簡単に解説していきます。
fakeデータって?
ここではテストを実行する際に用意する、 サービスの利用実態に合わせて捏造された事前データ のことを指します。
例えば 「ユーザーがログインできること」 をテストする場合、ログイン処理を実行させるために作られたユーザーアカウントのデータがそれに当たります。
ダミーデータじゃないの??
両者よく似た語ではありますが、ことテストにおいては下記のように微妙にニュアンスが違ってくるんじゃないかと思います(たぶん)。
- ダミーデータ ・・・ 本物を模して作られたハリボテ(モノとしての機能の有無は問わない)
- フェイクデータ ・・・捏造されたもの(偽物ではあるが、モノとしての機能は保持している)
家や建物で例えれば、ダミーの方は演劇の舞台装置などにあるような表層だけを模した単なる板切れであるのに対し、フェイクデータはモデルルームのような実際の生活空間を再現したもの、といったところでしょうか。
テストでは現実の振る舞いをより忠実に再現した上で検証できた方が望ましく、その意味で語としてもフェイクの方がよりフィットしそうな感じがしますよね。
rubyの faker
gemがこういう名前をしているのもそんな背景があるんじゃないかと思います。知らんけど。
で、なんで必要なの?
ではなぜフェイクデータが必要になるのでしょうか。
例えば以下のようなテストで考えてみましょう。
FactoryBot.define do
factory :user do
name { 'テストユーザー' }
email { 'user@example.com' }
end
end
RSpec.describe User, type: :model do
let(:user) { create(:user) }
let(:sent_email) { ActionMailer::Base.deliveries.last }
subject { user.send_welcome_message }
example '対象のユーザーにメールが送られること' do
subject
expect(sent_email.body).to include 'テストユーザー様'
end
end
送信されたメールに該当ユーザーの名前が入っていることを検証しているのですが、このテストには大きく3つの問題点が潜んでいます。
それぞれ見ていきましょう。
問題点 1. テストの検証内容が外部に依存している
このケースではFactoryにて作成された テストユーザー
という名前を期待してその文字列を検証しているのですが、ここに一つ目の問題があります。
この例の場合だと :user
というfactoryデータは決してこのケースに固有のものではなく、すべてのケースに対してオープンになっているものになります。
テストはそれ自体で完結できていた方が望ましく、外部要因でその成否が変わってしまうことはなるべく避けなければなりません。
例えば今回の場合、誰か別の開発者がFactoryの name
の値を直接変更してしまうと テスト対象の機能とは関係のない要因でこのテストがfailしてしまうわけです。
問題点 2. 特定の名前でしか検証しかできていない
記載の通り、このケースでは特定のユーザー名の場合しか検証できていないため 対象のユーザー名を参照していることが厳格に保証されていません 。
例えば極端な例ではありますが、以下のようにメールテンプレートでユーザー名を静的に埋め込んでいたとしても検証が通ってしまうことになります。これはよろしくありません。
テストユーザー様
会員登録ありがとうございます。
<%= @user.created_at %> に会員登録が完了しました。
現実の世界では登録ユーザーの名前は多種多様であり、テストにおいても様々な入力で正常に動作することを検証できた方が望ましい、ということになるわけです。
テストユーザー
という名前で登録するケースはあまりない
問題点 3. (おそらく) 現実に テストと言えどなるべく現実のユーザーの振る舞いや状況を模して検証すべきなので、 テストユーザー
という名前を検証の事前条件にするのはやはり適切とは言えないでしょう。
そこでfakeデータですよ
以上挙げ連ねてきた問題点を解消するため、ここでfakeデータの利用を検討してみましょう。
ここでは冒頭で言及した faker
というgemを利用してみます。
導入してみた
といっても公式通り以下を Gemfile
に記述してインストールするだけ
gem 'faker'
さらに日本語のfakeデータを作成する場合はlocaleを設定しましょう。
Faker::Config.locale = 'ja'
先ほどの例でみたテストデータは、fakerを使うことで以下のようになりました。
require 'faker'
Faker::Config.locale = 'ja'
FactoryBot.define do
factory :user do
name { Faker::Name.name }
email { Faker::Internet.email }
end
end
この段階で実行ごとに名前・メールアドレスが都度ランダムに捏造されるようになりました。
[1]pry(main)> Faker::Name.name
=> "渡部 太一"
[2] pry(main)> Faker::Name.name
=> "野村 彩"
つまり テストユーザー様
というユーザー名が含まれることを検証していた既存のテストケースはfailするようになったわけです。
それでは既存のテストで、ログインしたそれぞれのユーザーを厳格にチェックできるように修正しましょう。
RSpec.describe User, type: :model do
let(:user) { create(:user) }
let(:sent_email) { ActionMailer::Base.deliveries.last }
subject { user.send_welcome_message }
example '対象のユーザーにメールが送られること' do
subject
expect(sent_email.body).to include "#{user.name}様" # ここを修正
end
end
上記の通り修正することで、対象のユーザー名を明示的に指定するよう変更されて無事にテストがパスするようになりました。
先に挙げた問題点も解消されて、検証の精度としても向上しているのがわかりますね。
余談
ちなみにこの faker
gemですが実に多種多様なデータが用意されています。
pry(main)> Faker::JapaneseMedia::Doraemon.gadget
=> "Anywhere Door"
pry(main)> Faker::Quotes::Shakespeare.king_richard_iii_quote
=> "A horse! a horse! my kingdom for a horse!."
ドラえもんの秘密道具からシェイクスピア史劇の名台詞まで、「果たして使うシーンあるのか?」と思いたくなるものも含め非常に広範なジャンルが用意されてます。
この辺り眺めてるだけでも楽しいので是非一度試してみてください。
まとめ
以上まとめるとこんな感じです。
- テストは外部に影響されず、ケースそれ自体で完結できるようにしよう
- 色々なパターンでテストできるようにしよう
- なるべく現実世界に即した振る舞いをテストしよう
- 事前データを都合よく捏造してくれる便利なgemがあるので活用しよう
We are hiring!的な
クラッソーネでは各種エンジニアを積極採用中です!
少しでもご興味持っていただけたらお気軽にご連絡いただけると幸いです!
ありがとうございました。