既存プロダクトに
RSpec を
導入していく
-
rry と申します
- 平成.rb Organizer
- Vue.js 日本ユーザーグループ
- Vue Vixens
- フロントエンドも好きな Rails エンジニアです 🐣
〜 宣伝 〜
Vue.js は良いものだ
- Vue Vixens という
女性向けイベントやっています- Rails Girls みたいな感じ 😘
- 12/19(木)Vue Vixens #2 🎄🎅
- Nuxt.js アドベントカレンダー
9月〜
とあるプロダクトの
保守運用を任された
※ この物語はお仕事でのお話です
モノリシックな Rails
↓
API がいくつかある
↓
今年フロントエンドを Nuxt.js にリニューアル
↓
管理機能の画面はいまだモノリシックな Rails
※ この物語はお仕事でのお話です
RSpec のカバレッジ
5%
引き継ぎ当時...
RSpec をほぼ実装したことがない
よちよちメンバー 4人で保守運用
コードの中を確認...
-
RuboCop ... 入っていない
-
警告が出すぎて入れられない
-
-
バックオフィス向け管理機能
-
仕様を誰も把握していない
-
-
長すぎるメソッド...
-
改修しづらいコード...
-
使われていない機能...
🍝
逃げちゃダメだ!
既存プロダクトに
RSpec を
導入していく戦い
タイトル・改
これからどうする?
我々がとったアクション
コーチを召喚
ねらいは、メンバーの
スキルアップとテストの充足、
技術負債 🍝 の解消
どことは言いませんが、界隈で有名な
ESM さんにお願いしました。
我々がとったアクション
計画づくり
「テストがないとデグレーションしてしまう!」
「今は機能追加よりもテストやリファクタリングを優先させる」
CEO や他部署に合意してもらい、
技術負債解消ターンに入った
我々がとったアクション
実行
←今ココ
第 1 期:Unit test 系の Spec を充足させる
第 2 期: Integration test 系の Spec を充足させる
↑今ココ
第 3 期:リファクタリング 💪
テストの考え方について
テストの考え方について
基本的な自動テストの考え方や RSpec の書き方は
これらを読めば分かる
テストの考え方について
また、「使えるRSpec入門」シリーズ
なども大変お世話になっている。
これらを読んで基本が分かった
ところで、次は実践編 🚀
1. テストピラミッド、
上から降るか下から登るか
の詳しい説明は、
「初めての自動テスト」でも
紹介されています。
-
Unit テストは速く実行できる
-
シンプルな Unit テストでパターンを網羅
- model, decorator, uploader などの Spec
-
シンプルな Unit テストでパターンを網羅
-
Servise(結合)テストは Request Spec でまとめる
- Controller Spec などは省略して書かない
-
UI(E2E)テストは System Spec で書いていく
- 実行速度が遅いため、網羅的に書くには不向き
1. テストピラミッド、
上から降るか下から登るか
我々は model や decorator などの
Unit テストから書いていくことを
「計画づくり」のときに決めた。
- メンバーが RSpec に慣れている場合
- バグが起こったときに検知しやすいよう、
E2E などの外側のテストから書いていく
- バグが起こったときに検知しやすいよう、
- メンバーが RSpec に慣れていない場合
- 難易度が低くテストに慣れることができる
Model などの Unit テストから書いていく
- 難易度が低くテストに慣れることができる
2. テストを実装する前に、
describe、context などを使って、
まずはペンディングテストを書いていく
「何をテストしたいのか」をペンディングで書いていく
ことにより、頭が整理されるため
describe Article, type: :model do
# テスト対象(メソッド名など)
describe '#published' do
# 状態(〜の場合)
context '非公開記事がある場合' do
# 期待する出力(〜のこと)
it '公開記事のみ取得できること'
end
end
end
describe Article, type: :model do
describe '#published' do
context '非公開記事がある場合' do
before do
create(:article, name: '公開記事', published: true)
create(:article, name: '非公開記事', published: false)
end
subject(:article_published) { Article.published }
# 期待する出力(〜のこと)
it '公開記事のみ取得できること' do
expect(article_published).map(&:published)).to match_array([true])
end
end
end
end
- 「設計をテストで表現できる」のが RSpec の良さ
- 「ふるまい」を先にペンディングで書いていく(BDD)
- 機能実装するときも同じようにペンディングテスト
から書いていくと🙆
- 機能実装するときも同じようにペンディングテスト
3. あくまで「ふるまい」をテストすること
- マジックナンバーや、中身が分かりにくい
(または変動する恐れのある)値で検証するのは ❌ 👮
describe Article, type: :model do
describe '#some_data' do
context '何らかのデータがある場合' do
let(:bad_article) { create(:article, title: Foo.title, description: Foo.description, tag_type: 1) }
let(:good_article) { create(:article, title: 'タイトル', description: 'ディスクリプション', tag_type: 'Ruby') }
subject(:bad_some_data) { bad_article.some_data }
subject(:good_some_data) { good_article.some_data }
it 'データの配列が返ってくること' do
# 分かりにくい例 ×
expect(bad_some_data).to eq [article.title, article.description, 1]
# 分かりやすい例 ○
expect(good_some_data).to eq %w(タイトル ディスクリプション Ruby)
end
end
end
end
我々は全知全能だからコードの中身を知っているだけ。
メソッドの中身がどうなっていようと、期待する値を受け取れればそれで良い。
4. 「テストが書けない!」
- ひとつのメソッドに仕事をさせすぎている場合
- 中のロジックについて、テストを書くのが
あまりにも大変になる...
- その場合は、最低限テストで担保したい部分
(書ける範囲)を書き、その後にリファクタリングする- テストなしでリファクタするのは ❌
- 無理のある範囲をわざわざテストしなくても良い
- もし手動の動作確認で事足りるなら、最低限テストを書いてリファクタリングした後に、きちんとしたテストを書けば 👌
5. private メソッドのテスト?
- リファクタリングする前のコードにテストを書こうと
したら、プライベートメソッドのテストを書くはめに
- 書けなくもない... けど... 😑
- どうしても private メソッドをテストしたいときは、
設計がおかしいかもしれない- send でテストを書いた後で良いので、リファクタリングしていこう
- クラスに切り出すと幸せになれるかもしれない
let(:creator) { Admin::DataCreator.new() }
subject(:contract) {
# send を使って private メソッドを呼び出して検証することはできる...
creator.send(:foo, 1, 2, 3)
}
- 参考になった(気持ちがよく分かった)記事
- リファクタリングする前提で、private メソッドの
テストを書くのは致し方なしだと思う- いくつか(ガッツリ)private メソッドのテストがマージされている
- もちろんリファクタ対象になっている
- リファクタリングする前提で、private メソッドの
RSpec の
便利な書きかた Tips
1. これが便利 factorybot
- create で関連するモデルも作れる
- 最低限のバリデーションが通る factory を作ると、
応用が利いて便利
# Table name: users
# ...
# id :bigint not null, primary key
# name :string(255)
# coach_id :bigint
# ...
describe User, type: :model do
describe '.coachs' do
before do
create(:user, name: 'テスト A さん', coach_id: nil)
# 関連するモデルを作れる
create(:user, name: 'テスト B さん', coach: create(:coach))
end
subject(:user_coachs) { User.coachs }
context 'coach を持っているデータが存在する場合' do
it 'coach を持っている user が取得できること' do
expect(user_coachs.count).to eq 1
expect(user_coachs.first.name).to eq 'テスト B さん'
end
end
end
end
2. receive_message_chain に引数を
複数指定したい
describe 'Api::ContactsController', type: :request do
describe '.received_mail' do
subject(:request) {
post '/api/contacts', params: { contact: { name: '太郎', email: 'taro@co.jp', message: 'メッセージです' } }
}
# ...
# リクエストが成功することなどのテスト
# ...
# Api::Mailer.received_mail(Contact.new).deliver
before do
allow(Api::Mailer).to receive_message_chain(:received_mail, :deliver)
.with(instance_of(Contact)).with(no_args)
end
it 'mailer が呼び出されていること' do
request
expect(Api::Mailer).to have_received(:received_mail).once
end
end
- with を複数つなげることができる
- anything や no_args など引数マッチャを指定すると
より明示的にすることができる
3. 標準出力のテスト
describe Foo, type: :service do
describe Foo::Generator do
describe '.call' do
subject(:foo) { Foo::Generator.new }
it 'メッセージが標準出力されること' do
expect { foo.call }.to output("ファイルを出力しました。\n").to_stdout
# .to_stderr で標準エラー出力も検証できる
# expect { foo.call }.to output("エラーが発生しました。\n").to_stderr
expect(File.exist?(file)).to be true
end
end
end
emd
- RSpec にある output().to_stdout が便利
- output マッチャ以外にも便利なマッチャが沢山ある
- テストを書くときは RSpec の公式ドキュメント
Relish を読む
まとめ
まとめ
-
RSpec の良いところ
- Spec がUnit テスト系から Integration テスト系まで手厚く用意されており、過不足なくテストが書けること
-
DSL で読みやすい
- 読みやすいということは、ドキュメントとして仕様を把握しやすい
-
モックが簡単に書けたり、気の利いたマッチャが多く、
テストが書きやすい- 書きやすいということは、機能の修正やリファクタリングがしやすい
つまり...?
変化に強くなれる💪
- Rails や Gem のバージョンアップ・導入
- 仕様変更や機能追加がしやすくなる
- テストがあることによりこれらに
安心感が持てる
今のプロダクトがリファクタリング期に
入るのは、あともう少しだけかかりそう
ですが...
少しずつ開発に
安心感が持てるようになってきた 🎉
-
System Spec とあと少しのテストを除いてテストが充足してきた
-
機能改修にも着手しつつある
-
-
コーチに
圧倒的感謝 🙏
ちなみに...
RSpec のカバレッジ
5%→15%
俺たちの戦いは...
まだ始まったばかりだ...!
ところで...
突然ですが...
rry、1月から
STORES.jp に転職します。
- 12月いっぱいまで
- フロントエンド(Nuxt.js)側の引き継ぎ
-
メンバーひとりひとりが RSpec の実装に慣れてきた
- チームの雰囲気と開発速度が安定してきた
- 新しいことにチャレンジしたいと思った 💪
ご静聴
ありがとうございました!
詳しい話や質問は懇親会
でお声がけください 🙋
引き続き平成 Ruby 会議を楽しみましょう!
逃げちゃダメだ!既存プロダクトに RSpec を導入していく戦い
By rry
逃げちゃダメだ!既存プロダクトに RSpec を導入していく戦い
平成 Ruby 会議 01 での発表資料です。
- 3,508