- バックエンド
- PdM
- フロントエンドエンジニア
- Other occupations (23)
- Development
- Business
have_attributes マッチャーを活用して失敗原因の特定がしやすいテストを書く
こんにちは。ウォンテッドリーの Enabling チームでバックエンドエンジニアをしている市古(@sora_ichigo_x)です。
現在、Enabling チームでは技術的な取り組みを社外にも発信すべく、メンバーが週替わりで技術ブログをリレー形式で執筆しています。前回は小室さんによる「Amazon BedrockでClaudeから構造化した回答を取得する際に得た知見」でした。今回はRSpecの話をします。
失敗原因の特定がしやすいテストを書く
RSpec で実装しやすく、保守しやすいテストを目指すなら、失敗原因の特定がしやすいテストを書く必要があります。ここでいう「失敗原因の特定がしやすいテスト」とはテストが落ちた時に、どの部分が壊れているのかをほぼ一意に絞り込めるテストを指します。「失敗原因の特定がしやすいテスト」は次のような特徴を持ちます。
- 単一原因で失敗する
- 1つのテストが複数の概念を検証していないため、失敗時の原因が明確になる
- 観測点がシンプルで読みやすい
- expect があちこちに散らばらず、何を確認しているのか一目で分かる
- 前提条件が最小限かつ明示的である
- 無駄なデータや設定がなく、テストケースの意図がすぐに伝わる
この性質は xUnit Test Patternsでは Defect Localization と呼んでいます。Defect Localization を向上させるテクニックは1つではありませんが、今回はその中でも have_attributes マッチャーを活用する例を紹介します。
have_attributes マッチャーを活用する
テスト対象のメソッド実行前後で "あるオブジェクト" の属性が意図通りに変化しているかを確かめる機会があったとします(実際よくあります)。その際、以下のように expect を複数行並べるコードを見たことがあるかもしれません。
RSpec.describe User do
describe '.create_with_profile' do
it '正しい属性で作成される' do
user = User.create_with_profile(name: 'Alice', email: 'a@example.com', age: 23)
expect(user.name).to eq 'Alice'
expect(user.email).to eq 'a@example.com'
expect(user.age).to eq 23
# --- 実行時の失敗ログ例 ---
# 1) User.create_with_profile 正しい属性で作成される
# Failure/Error: expect(user.email).to eq 'a@example.com'
#
# expected: "a@example.com"
# got: nil
#
# (compared using ==)
# # ./spec/models/user_spec.rb:7:in `block (3 levels) in <top (required)>'
# ----------------------------
# 実際の原因:
# create_with_profile メソッドが email をセットしていない
# 他にあり得るかもしれないがこの結果からは読み取れない問題:
# create_with_profile が age もセットしていない
end
end
end
上記のように属性数が少ない場合においては、これが致命的に悪いわけではありませんが、より失敗原因の特定がしやすいテストを書くには have_attributes マッチャーを使って expect を1つにまとめる方法がおすすめです。
# 情報を1箇所に集約(差分が読みやすい)
RSpec.describe User do
describe '.create_with_profile' do
it '属性が正しい' do
user = User.create_with_profile(name: 'Alice', email: 'a@example.com', age: 23)
expect(user).to have_attributes(
name: 'Alice',
email: 'a@example.com',
age: 23
)
# --- 実行時の失敗ログ例 ---
# 1) User.create_with_profile 属性が正しい
# Failure/Error:
# expect(user).to have_attributes(
# name: 'Alice',
# email: 'a@example.com',
# age: 23
# )
#
# expected attributes:
# :name => "Alice",
# :email => "a@example.com",
# :age => 23
# got:
# :name => "Alice",
# :email => nil,
# :age => nil
#
# # ./spec/models/user_spec.rb:6:in `block (3 levels) in <top (required)>'
# ----------------------------
# 実際の原因: create_with_profile メソッドが email と age をセットしていない
end
end
end
元の expect を複数行繰り返す書き方では、email が nil になる問題を解消した後、テストを再実行した際に age が nil になる問題に気づき2度手間が発生するかもしれません。一方で have_attributes マッチャーを使えば、全ての属性の差分を一度に確認できるため、1回のテスト実行で問題を把握できます。
上の例だと些細な恩恵にすぎませんが、テストが複雑化していくとこの小さな気遣いが開発者にとって失敗原因を特定しやすい、落ちて嬉しいテストに繋がります。