Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
実録レガシー Rails アプリ改善ジャーニー / Legacy Rails app impr...
Search
shutooike
March 25, 2023
Programming
3
800
実録レガシー Rails アプリ改善ジャーニー / Legacy Rails app improvement journey
第90回 Ruby関西 勉強会で発表したスライド資料です!
https://rubykansai.doorkeeper.jp/events/150547
shutooike
March 25, 2023
Tweet
Share
Other Decks in Programming
See All in Programming
CSC509 Lecture 11
javiergs
PRO
0
180
Arm移行タイムアタック
qnighy
0
320
レガシーシステムにどう立ち向かうか 複雑さと理想と現実/vs-legacy
suzukihoge
14
2.2k
ピラミッド、アイスクリームコーン、SMURF: 自動テストの最適バランスを求めて / Pyramid Ice-Cream-Cone and SMURF
twada
PRO
10
1.3k
Amazon Qを使ってIaCを触ろう!
maruto
0
410
Jakarta EE meets AI
ivargrimstad
0
560
Hotwire or React? ~アフタートーク・本編に含めなかった話~ / Hotwire or React? after talk
harunatsujita
1
120
ヤプリ新卒SREの オンボーディング
masaki12
0
130
3 Effective Rules for Using Signals in Angular
manfredsteyer
PRO
0
110
Click-free releases & the making of a CLI app
oheyadam
2
120
型付き API リクエストを実現するいくつかの手法とその選択 / Typed API Request
euxn23
8
2.2k
[Do iOS '24] Ship your app on a Friday...and enjoy your weekend!
polpielladev
0
100
Featured
See All Featured
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
38
1.8k
Embracing the Ebb and Flow
colly
84
4.5k
It's Worth the Effort
3n
183
27k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
Code Reviewing Like a Champion
maltzj
520
39k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
880
GraphQLとの向き合い方2022年版
quramy
43
13k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.1k
Building Adaptive Systems
keathley
38
2.3k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
665
120k
Transcript
実録レガシー Rails アプリ改善ジャーニー 2023.3.25 第90回 Ruby関西 勉強会 / 舘林 秀和
@shutooike
自己紹介 舘林秀和 / Shuto Tatebayashi 所属 株式会社インゲージ 各種アカウント @shutooike 途中で名字が変わってややこしい
趣味 ひとこと コミュニティで発表するのが初めてなの で緊張してます
目次 ジョイン当時の状況 課題 改善ジャーニー 自動テスト編 バージョンアップ編 リファクタリング編 得られたもの これから まとめ
ジョイン当時の状況 2014年から開発している Rails アプリ フロントエンドはSPA バックエンドはAPI+Job サービスはたくさんのユーザーに使われている Ruby のバージョン2.3 RSpec
が導入されているが、カバレッジは20%以下 テストがないことがほとんど=レガシーコード QAはステージング環境でのドックフーディングと手動テスト頼み CIはあった(werkerを使用) 週に1回本番リリース
課題 テストが継続的に書かれてない 簡単な不具合修正、機能改善、負債返却で他が壊れることがある 以前と変わることは手動テストで担保するが 以前と変わっていないことを担保できていない ステージング環境によるドッグフーディングには限界が 変更に対する心理的安全性が低い 言語・ライブラリのバージョンアップが継続的に行われていない EOL切れ バージョンアップ時の防衛線がステージング環境にしかいない
同じようなロジックがあちこちに点在
改善ジャーニー 次のスライドから取り組んできたことおおよそ時系列で紹介していく この発表で説明しきれない詳細は今後ブログに書いていくつもり Twitter で @shutooike をフォローして、読んで、拡散して、感想ください(欲張 り)
自動テスト編
実行順序のランダム化 ファイル名順での実行になっており順番に依存したテストがちらほら spec ファイルを追加すると突然落ちるようになったり config.order = :random テスト数がそこまで多くなかったのでエイヤで :random にして、ローカルやCIで落ち
たら都度修正 Kernel.srand config.seed とすると Array#sample も seed で再現するようになる
テストデータが残らないように CIで特定のテストが失敗することが稀によくある 他のテストで作ったデータが原因 example ごとにデータを消す config.use_transactional_fixtures = true before(:all) で作りっぱなしのデータ
before(:each) に変更 after(:all) で削除
自動テストを書いていく(1) 日々の開発で自分が関わったコードからテストを書いていく なぜか書きにくいという実感が... 原因突き詰めたところ →「そうか!土台が出来てないんだ!」 プロジェクト発足
実際の最初の issue
スキップしていたモデルのコールバックを解放 User.skip_callback(:create, :after, :foo) 思考停止でのほぼ全てのコールバックがスキップされていた プロダクトコードではデータ作成時にコールバックで行っている処理を、テストデ ータ作成時は自分で明示的に処理しないといけない状況 テスト環境の挙動は出来る限り本番環境に近づけるべき 一部残したコールバックもある 時間がかかりすぎるもの
外部サービスを呼び出すもの モック化するという手もある
FactoryBot のフル活用 お馴染みのテストデータ生成ライブラリ シナリオに沿ったテストデータをサクッと作れないとテストを書くハードルが一気に上 がる 導入はされているが、上手く運用はされていない状況 FactoryBot の定義方針を検討・決定 詳しくはブログに書きます 方針に沿って
FactoryBot を再定義・既存のテストを修正 最初は頑張って全モデルでやろうとしたがその必要はなかった よく出てくるスタメンモデルだけでOK
自動テスト戦略を立てる ここまでテストを書いた感覚でテスティングピラミッドを目指すのは厳しいと判断 単体テストを継続的に書くのは開発者の力量に依存するところが大きい 良くない単体テストはむしろ開発生産性を下げる プライベートメソッドのテストを書く 期待値生成にコードと同じロジックを用いる... など まずは統合テストたくさん書いていくことにする 目指すはテスティングトロフィー 主な
Rails の責務である API の request spec を書いていく とりあえずある程度までカバレッジを上げたい
誰でも書ける request spec helper を用意 誰でも書けるように薄さにこだわる 正常系のみ 最低限の条件 ログインユーザー パラメーター
最低限の検証 APIの権限チェック HTTPステータスコードチェック APIを作ったら絶対書こうねという意識付け →「お約束」というネーミング
利用側のイメージ describe 'POST /api/messages' do context ' お約束' do let(:login_user)
{ create(:normal_user) } let(:minimum_required_params) { { text: 'Hi' } } it_behaves_like(:can_be_accessed_with, 'normal') it_behaves_like(:return_http_status_as, :created) end end どのように実装したかはブログに書きます
全APIに正常系テスト(お約束)を書いていく 全体カバレッジが60%ほどに APIのカバレッジは90%近くに 少なくとも最低限の正常系の動作が変わっていないことが担保できるようになったので 安心感が全然違う コスパ最強
request spec でAPIの振る舞いを検証する クリティカルユーザージャーニー(CUJ)を定義 ユーザーが 1 つの目的を達成するために行うサービスとの一連のインタラクション CUJをもとにサービスの主要な操作に関するAPIに対して正常系・異常系の振る舞いを検 証 振る舞いとは?
データのCRUD ジョブのキック 外部サービスの呼び出し... など 主要なAPIの動作の担保され安心感がさらに上がった
最低限のE2Eテストを書く SPAといいつつ複数画面がある メイン画面 編集画面 外部連携用画面... など 最低限の検証 画面が正常に開けることのみ確認 ステージング環境でのドックフーディングでは普段開かない画面がひっそり死んでるこ とに気づけるように
バージョンアップ編
Ruby 2.3 → 2.7.6 警告の対応 依存 gem のバージョンアップ 開発環境でバージョンアップ 自動テスト・手動テスト
他の開発者の開発環境に展開 ステージング環境バージョンアップ 手動テスト 本番環境バージョンアップ
Rails 5 → 6 警告の対応 依存 gem のバージョンアップ 開発環境でバージョンアップ rails
app:update new_framework_defaults 自動テスト・手動テスト ステージング環境バージョンアップ 手動テスト 本番環境バージョンアップ
リファクタリング編
複数モデルで使える共通の処理を concern に Rails がデフォルトで用意している仕組み 上手く使えたら綺麗に抽象化できる 上手く使えたら... 例 created_by, updated_by
を入れる処理
単体テストが書きやすくなるような実装に 自動テスト戦略で統合テストに注力したもう1つの理由として単体テストが書きにくい実 装になっていた ドメインロジックがコントローラー層に Fat Controller より単体テストが書きやすいようにドメインロジックをモデルに寄せていく必要がある Fat Model 自動テストがある程度書けたので振る舞いが変わらないことを担保しながらリファクタ
リングができる
[再掲] 課題 テストが継続的に書かれてない 簡単な不具合修正、機能改善、負債返却で他が壊れることがある 言語・ライブラリのバージョンアップが継続的に行われていない EOL切れ バージョンアップ時の防衛線がステージング環境にしかいない 同じようなロジックがあちこちに点在
得られたもの テストが書きやすい環境 APIを作成したらお約束テストを追加する文化 ある程度のカバレッジ コードの変更やライブラリのバージョンアップなどに対する心理的安全性 他に影響があるコード変更がCIで落ちる ライブラリのバージョンアップの非互換に気付けたことも
[再掲] 課題 テストが継続的に書かれてない 簡単な不具合修正、機能改善、負債返却で他が壊れることがある 言語・ライブラリのバージョンアップが継続的に行われていない EOL切れ バージョンアップ時の防衛線がステージング環境にしかいない → 継続してやっていく 同じようなロジックがあちこちに点在
→ 継続してやっていく
これから とりあえず「EOLを切らない」を目標に Ruby, Rails を上げていく dependabot をフル活用してライブラリアップデートを習慣化 テスティングピラミッドを目指す 単体テストが書きやすくなるよう引き続きリファクタリング ステージング環境で行っている手動テストの負荷低減
現在の最低限のE2Eから少し検証範囲を広げる
まとめ 自動テストは書かないと上手くならない 勘所を掴むまでクソテストコードを書いて書いて書きまくれ 幸いテストコードは書き直しやすい レガシーコード改善に銀の弾丸はない カバレッジも可読性、メンテナビリティも突然上がることはない 理想に向けてロードマップを引き地道にやっていくしかない レガシーコードを愛する ユーザーの課題を解決し、売上を立てるレガシーコードは偉大 なりたくてレガシーコードになった訳じゃない
その時々の最適解だったはず 恨みではなくリスペクトを持って接する
おまけ1 カバレッジは正しく測ろう 以下の2つを行ったらカバレッジがグッと上がった # config/envioroments/test.rb config.eager_load = true spring を使わずに
RSpec を実行 原因ははっきりわかっていないがおそらくコードの分母が正しく測れるようになったか ら 知ってる人いたら教えてください
おまけ2 カバレッジを追い求めすぎない カバレッジが高いからバグが出ない訳じゃない 効果的なテストをすることが一番重要 カバレッジは目標にせず、指標にするべき
None
None
None