はじめに
初学者の私は、RSpecでテストを書いていると、つい「動くかどうか」だけに目が行きがちになっていることに気づきました。
学習をしていく中で「テストは仕様書でもある」という考え方を知りました。
この記事では、実際にブログ投稿アプリを作りながら気づいたポイントを備忘録として残します。
学んだ3つのポイント
1. it の記述は「仕様」として読めるように
Before(実装寄り)
context "通知メールの初期状態" do
let(:post) { create(:post, user: user, category: category, publish_date: Date.tomorrow, scheduled_at: "10:00") }
it "is_sentがfalseである" do
# ...
end
end
After(仕様寄り)
context "通知メールの初期状態" do
let(:post) { create(:post, user: user, category: category, publish_date: Date.tomorrow, scheduled_at: "10:00") }
it "未送信として記録する" do
# ...
end
end
学んだこと
contextやitのメッセージを実装寄りにせず、なるべく「仕様」寄りにするのが重要。
仕様は、エンジニアじゃない人/コードを知らない人が読んでも意味がわかるように。
ポイント
-
is_sentという変数名ではなく、何が起こるのかを説明する - 非エンジニアでも理解できる表現にする
- テストは「仕様書」としても機能すべき
2. メソッドのテストは describe でまとめる
Before(構造が不明瞭)
context "通知メールが作成された場合" do
# create_notificationsメソッドのテスト内容
end
context "即時通知が作成される場合" do
# さらにcreate_notificationsのテスト
end
After(メソッド単位で構造化)
describe '#create_notifications' do
context '記事公開が明日の場合' do
it '即時通知を作成する' do
post.create_notifications
# 実行後の期待値を書く
end
end
context '記事公開が2日以上先の場合' do
it '前日通知を作成する' do
post.create_notifications
# 実行後の期待値を書く
end
end
end
学んだこと
この部分は
#create_notificationsメソッドのテストになる。
そうであれば構造としてはdescribe '#メソッド名'でまとめるのが自然。
ポイント
- メソッドのテストは
describe '#メソッド名'で囲む - どのメソッドをテストしているのか明確になる
- テストの階層構造が整理される
3. context は条件分岐がある場合に使う
Before(不要なcontext)
describe '#create_notifications' do
context "即時通知が作成される場合" do
it "即時通知のタイトルが正しい" do
# ...
end
it "即時通知の内容が正しい" do
# ...
end
end
end
問題点: create_notifications メソッド内に分岐がないのに context を使っている
After(シンプルに)
describe '#create_notifications' do
it '即時通知のタイトルが正しい' do
post.create_notifications
# 実行後の期待値を書く
end
it '即時通知の内容が正しい' do
post.create_notifications
# 実行後の期待値を書く
end
end
学んだこと
contextは、実装側(プロダクションコードと呼んだりする)でifやunlessなど実行に条件がある場合に使う。
一方create_notificationsメソッドの実装を見ると分岐は無いためにcontext自体が不要。
ポイント
-
contextは条件分岐に対応させる - 分岐がなければ
itを直接並べる - 無駄なネストを避ける
実装に分岐がある場合の context の使い方
もし create_notifications に以下のような分岐があれば、context が有効
def create_notifications
if publish_date == Date.tomorrow
# 即時通知を作成
else
# 前日通知を作成
end
end
この場合のテスト
describe '#create_notifications' do
context '記事公開が明日の場合' do
it '即時通知を作成する' do
# ...
end
end
context '記事公開が2日以上先の場合' do
it '前日通知を作成する' do
# ...
end
end
end
まとめ:良いテストの3原則
| 原則 | 説明 |
|---|---|
| 仕様として読める |
it は非エンジニアでも理解できる表現にする |
| 構造を明確に | メソッドのテストは describe '#メソッド名' で囲む |
| contextは分岐に対応 | 実装に条件分岐がある場合のみ context を使う |
RSpecの基本構造
describe 'クラス名またはメソッド名' do
context '特定の条件下では' do
it '期待される振る舞いをする' do
# テストコード
end
end
end
- describe: 何をテストするか(クラス、メソッド)
- context: どんな状況か(条件分岐)
- it: 何が起こるべきか(仕様)
おわりに
テストコードは「将来の自分や他の開発者への説明書」。実装の詳細ではなく、何が保証されるべきかを明確に書くことで、保守性の高いテストになるようです。
学びを通じて、テストが単なる検証ツールではなく、仕様を伝えるドキュメントでもあること知りました。
実践する際のポイント
- PRレビューで「itの説明が変数名っぽい」と言われたら、仕様寄り表現を意識してみる
- describeやcontextを一度ツリー構造で整理してみる
- 他の人のRSpecを読んで、どんな言葉で仕様が書かれているか観察する
参考資料
初学者のため、間違えていたらすいません。