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 Fixtures再考
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Masatoshi Iwasaki
September 12, 2019
Programming
10k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Rails Fixtures再考
Masatoshi Iwasaki
September 12, 2019
More Decks by Masatoshi Iwasaki
See All by Masatoshi Iwasaki
不安定テストを生み出すCapybaraを調教する
mstshiwasaki
3
1.4k
サービス開発初期の「時間を金で買う」技術
mstshiwasaki
6
6.3k
ふつうのRails開発を続けるために
mstshiwasaki
18
7.2k
Crafting Rails 4 Application 読み会(第6章)
mstshiwasaki
0
450
Crafting Rails 4 Application 読み会(第5章)
mstshiwasaki
0
420
Railsで使えるエラー通知系サービスまとめ
mstshiwasaki
0
710
Other Decks in Programming
See All in Programming
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
520
【SRE NEXT 2026 Lunch Session】一人目専任SREの立ち上げを加速する ― AIと進めたオンボーディングで2分を0.04秒にした話
pkshadeck
PRO
0
3.5k
Detecting Compromised CI with eBPF and Cilium Tetragon
lizrice
0
130
「人を評価する AI」の設計と実装
ryoyanara
0
140
AIが無かった頃の素敵な出会いの話
codmoninc
1
380
変わらないものが、変わるものを決める — 意図駆動開発 × イベントソーシング × イミュータブル | What Doesn't Change Decides What Can — IDD × Event Sourcing × Immutability
tomohisa
0
1.2k
霧の中の代数的エフェクト
funnyycat
1
460
yield再入門 #phpcon
o0h
PRO
0
930
Android CLI
fornewid
0
210
属人化した知識を、 AIが辿れる地図にする
pkshadeck
PRO
1
140
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
150
テーブルをDELETEした
yuzneri
0
130
Featured
See All Featured
Facilitating Awesome Meetings
lara
57
7.1k
New Earth Scene 8
popppiees
3
2.5k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
520
How to build a perfect <img>
jonoalderson
1
5.8k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
460
Become a Pro
speakerdeck
PRO
31
6.1k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
190
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.5k
Evolving SEO for Evolving Search Engines
ryanjones
0
250
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
290
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
480
Transcript
Rails Fixtures 再考 2019/09/12 銀座Rails #13 Masatoshi Iwasaki
自己紹介 Masatoshi Iwasaki GitHub: masa-iwasaki Twitter: masa_iwasaki 今月からフリーランスのRailsエンジニアになりました (3.5年振り2回目)
今日のテーマ Rails Fixtures 再考
そもそも 再考 する前に
会場アンケート - Fixtures知っている人? - Fixtures使っているプロジェクトに関わったことがある 人? - Fixtures実際に書いたことがある人? - テストデータFixturesだけで書いたRailsプロジェクトが
ある人?
Fixturesとは Railsに標準で用意されているテスト用サンプル データを用意する仕組み。 Rails GuidesのTestingに関するセクション(右記) に説明がある。 https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures
Fixturesの作り方 • YAMLで記述 ◦ 1ファイル1モデル • ラベルを使うことでレコードを識 別できる ◦ 右の例だと
david と steve • 記載した内容がそのままデータ ベースに挿入される ◦ ActiveRecordインスタンスの 生成を介さない ◦ よってvalidationも行われない https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures
Fixturesの参照方法 • 対応するテーブルごとにメソッドが用意される ◦ rspecの場合は上記のようにどの fixtureを使うかを明示的に指定 ◦ `config.global_fixtures` で spec
全体で使えるようにすることも可能 • 上記の例だと、User / Organization モデルのfixturesで挿入されたデータを `users` / `organizations` メソッドで参照可能にしている。 ◦ `users(:LABEL_NAME)` で対応するレコードの ActiveRecordインスタンスを取得できる。
Fixtures YAMLでできること • erbを使って動的に値を生成したり、ラベルを使ってassociationを簡単に記載できる。 ◦ 時間の都合で割愛。 • ActiveRecord::FixtureSet のドキュメントに詳細がまとまっています。 ◦
https://api.rubyonrails.org/v6.0.0/classes/ActiveRecord/FixtureSet.html ◦ 日本語訳もあります。 https://techracho.bpsinc.jp/hachi8833/2019_07_11/77512 • RubyKaigi 2014の Deep down fixtures のスライドも手短に内容がまとまっている ◦ http://rubykaigi.org/2014/presentation/S-PrathameshSonpatki/ ◦ ...と思ったらいつの間にかスライドが404だった • 登壇者が追加として記載した以下のTipsも役立つ ◦ https://blog.bigbinary.com/2014/09/21/tricks-and-tips-for-using-fixtures-in-rails.html
FactoryBot vs Rails Fixtures • (たぶん)多くの開発現場ではテスト用サンプルデータ作成に FactoryBotのほうが多用されている • Fixturesに対する否定的・批判的な意見が多く、中には “Fixturesは使うな”
という意見もある。 ◦ Rails Testing Antipatterns: Fixtures and Factories (https://semaphoreci.com/blog/2014/01/14/rails-testing-antipatterns-fixtures-and-factories.html) • 否定体な意見のポイントは概ね以下のように集約できる(と思う) ◦ 「テストとテスト対象データが離れている」 ◦ 「テストデータの管理が大変」 ◦ 「テストしたい項目に対して不必要なデータが読み込まれる」 • FactoryBotを開発しているthoughtbotが書いている以下の記事がシンプルかつ要点を抑えている ◦ Mystery Guest (https://thoughtbot.com/blog/mystery-guest)
しかし実際は... • Fixturesへの批判はFactoryBotを使ったとしても結構当てはまってしまう • 「テストとテスト対象データが離れている」 ◦ 自分ひとりでfactoryを書いているうちは問題ないが、複数人でテスト書いていると factoryの定義すべてを確認 するわけでもなく、ブラックボックス化しやすい •
「テストデータの管理が大変」 ◦ FixturesとFactory、それぞれで辛い点がある。 ◦ associationがどんどん増えていくとどちらにしても管理は辛い • 「テストしたい項目に対して不必要なデータが読み込まれる」 ◦ これはこれで正しい。 ◦ が、本番環境に近い状況を再現したいテストの場合、テストに必要なデータ「だけ」があるよりも多様なデータが あるほうが問題に気づきやすい、という見方も可能。
ということで、今日のテーマ Rails Fixtures 再考
Fixtureを使うメリット • テストデータの生成が速い ◦ DBに直接データを流し込むだけなので、 FactoryBotと異なりARインスタンス生成コスト がなくなる ◦ 反面、validationは行われず、各種callbackも当然実行されない •
`use_transactional_test` との相性が良い。 ◦ テスト実行時に一度だけデータを流し込み、後のテストはすべてトランザクションで処理 し、ロールバックすることでクリーンナップ処理が高速化される。 ◦ Rails 5.1で導入されたSystemTestを使えばCapybaraを使ったテストでも `use_transactional_test = true` で問題ない。rspecで既存のfeature specを system spec に切り替えることもそれほど手間なくできる。
湧き上がる素朴な疑問 FactoryBotをFixturesに 置き換えたらテスト早く なるのでは?
どうやって検証するか? • 簡単なプロジェクトだと実際のプロジェクトと比較しづらい ◦ 実際に動いているRailsプロジェクトが良い • あまりにでかいプロジェクトだと置き換えるのがつらい。 • かつ、OSSだと具体的な話を公開・共有できるので理想。
そんな条件を満たすプロジェクトあるの?
ありました https://github.com/thepracticaldev/dev.to
dev.toとは? - ソフトウェアエンジニアが技術情報を投稿するサイト ( - https://dev.to - ざっくりいうとQiitaみたいなもの - 一時期「表示が爆速」ということで話題になった
- 2018/08からサービスのソースコードがオープンソースになっている - https://dev.to/ben/devto-is-now-open-source-5n1 ちょうど一年前の ginza.rbでテーマに取り上げられていまし た。 https://ginzarb.doorkeeper.jp/events/79974
dev.toでFactoryをFixtureに置き換えられるか? • dev.toで実験的に置き換えを行えば、それなりに現実的な知見が得られる。 • (もし)それなりの高速化ができれば fixtureのメリットについて評価が可能。 ◦ 置き換えのコストと高速化により減少したテスト実行時間とのトレードオフ
レギュレーション • 完全な置き換えは難しいと思われるので、やれるところだけやる。 ◦ 最初は「全部置き換えてやるぜ!」と思っていたら無理ゲーでした • 置き換えるのは model specのみ ◦
system specではCapybaraのオーバーヘッドが大きくてテスト時間が減少しても評価しづ らいと想定 • upstreamにPRしない想定 ◦ あくまで実験 ◦ fixtureのラベル名がかなり雑だったり、謎にこける shoulda-matcherなどはコメントアウト したり • 単純にFactoryからFixtureに変換すれば、変えた分だけ速くなる、はず。 ◦ dev.toは Rails 5.2 で `use_transactional_test = true` になっている
dev.toのざっくりなモデル構成解説 • User / Organiztion ◦ GitHubのように1ユーザが複数組織に所属可能 ◦ Organizationは組織用のページを持てたりする。詳細は https://dev.to/organization-info
を参照 • Article ◦ 記事に対応するモデル ◦ ここにコメントや投票とかがいろいろ紐づく • モデルやテーブルの数はそれほど多くない(個人的印象)
Factory -> Fixture置き換えの手順 • GitHub上でfork ◦ masterの最終コミット(0133cf1)が2019/07/26時点のもの • Facotry定義ファイルをざっくりとFixturesのyamlに置き換えるgemを作成 ◦
https://github.com/masa-iwasaki/factory_scrap ◦ マジでざっくりなので使うときはほんと注意してください ◦ transientとかファイルアップロードとかは自動的に直せないため、手動で修正が必要。 • app/models/*.rb で create(: で始まる箇所を検索。 • 手作業で fixtureに置き換える。 • 必要な箇所だけ `fixtures :users` のように fixtureを参照して、global_fixturesや `:all` は使わない。 ◦ `fixtures :users` が実行された段階で users テーブルに存在するレコードがすべてインスタンス化 されるため、rspecの global_fixtures に指定すると無駄なインスタンス生成が起こる(はず)。
置き換えてみた • なぜか何もしていないのにこけるテストが2件ほどあったので、そこについては飛ばした ◦ どれもshoulda-matcher関連なので実行速度への影響は軽微。 • CIで安定的にテストが通るところまでもってこれた ◦ https://github.com/masa-iwasaki/dev.to/pull/1 ◦
Forkしたmasterでfixtureとは無関係にコケるテストを修正した比較用PRも用意 https://github.com/masa-iwasaki/dev.to/pull/2 • 手作業で修正・追加したfixtureについては実装速度優先でだいぶ雑に記載 ◦ コピペや雑なラベル名(user_1, user_2)など。 • model spec 52ファイル中、25ファイルで一部 or 全部fixture置き換え ◦ 全体で641 examplesのうち、247 examplesが今回の置き換えで影響を受けたテスト。
実効速度の比較
計測方法 • forkした時点のmasterとfixture対応したブランチでの “rspec spec/models” 実行時間比較 ◦ 実行時間はrspecが最後に出力する “Finished in
….” の値を採用 • 手元の開発環境でなるべく余分な負荷をかけずに10回ずつ実行 ◦ 開発環境は Ubuntu 18.04 on Hyper-V (Windows 10) ◦ 厳密にやるならLinuxが動いている実機でやるべきですが、自宅に都合の良いマシンが 落ちてなかった...
実行速度比較結果 約13秒(平均)の実行時間減少。
TravisCI上での比較 1度しか走らせていないものの約 15秒の改善と開発環境での実行時間差に近い。 master: 181.4 seconds / fixture置き換え: 166.5 seconds
181.4 - 166.5 = 14.9 ↑ master / ↓ fixture置き換え
Factory -> Fixture してみた感想
辛かったところ • associationが無いモデルは楽勝(当たり前) • callbackの実行をテストしているところは引き続きFactoryを使う ◦ before_create / before_validationとか ◦
特にArticleはcallbackが多く、associationでArticleを参照しているモデルのmodel specでarticleの callbackによる変更を確認したりするテストをfixture化するのはつらい。 • Factoryで transient や 各種callbackを使っているところFactory継続 ◦ Factory不要の状態に持っていくことは可能だが、Fixtureに直すという趣旨からは外れる • ファイル添付可能なモデルもFactory継続 ◦ dev.toはCarrierWaveを利用。
Fixture一番の弱点:ラベル(名前)付けが必要 • fixtureでは常にテストデータがすべて DB上に存在する ◦ よって、識別するための名前付けが必要。 • 大量の組み合わせデータを作るのが辛い ◦ 「複数Userが別々のOrganizationに所属してそれぞれ記事を書いている」というテストデータと「複数
Userが 別々のOrganizationに所属してそれぞれ書いた記事にコメントつける」というテストデータが常時 DBにあって、 それぞれをspec側から区別できるネーミングをつけるというのがとにかく辛い。 ◦ 適切に名前付けして識別できればデータベース上にテストに必要な全データが入っていてもそれほど大変では ない(ような気がする) • Factoryは無名関数的に使えるのが強いのだと実感 ◦ テスト実行中にしかデータが存在しないので、コンテキスト内で変数名で識別できれば十分 ◦ trait等を利用するまでは名前付けを遅延できる
気づいたこと • 改めて見直すと「別にFactory使わなくていいよね?」というテストも見つかる。 ◦ thoughtbotのブログでも「ほとんどのユニットテストは永続化されたデータ(=DBへの保存)は要らない からfactoryを使わないほうがテストを高速化できるよ」という記事を書いている。 ▪ Speed Up Tests
by Selectively Avoiding Factory Girl (https://thoughtbot.com/blog/speed-up-tests-by-selectively-avoiding-factory-girl) ◦ FactoryBot自体が ひたすら instance_eval でファクトリーの定義を評価していて、ARインスタンス生成コ スト以上のコストが掛かっていることも考慮したい。 • ARのcallbackを多用しているモデルは置き換えづらい。 ◦ 反対にマスターデータなどはFixturesで予め流し込んでおくほうが良い。 • Fixturesで流したデータのvalidationは必須 ◦ expect(user).should be_valid というようなテストでfixtureの不備に気付かされたことが多数。 ◦ テストとは別にfixturesのデータがvalidかどうかをチェックするrake taskがあればいいのかも。
テスト実行時間削減としてのFixture利用 • 既存のテストへの導入はコスパが悪いかもしれない ◦ 短期的に作業時間にかかるエンジニアの人件費のほうがCIでインスタンス追加するより高くつく ◦ 超長期で見たらペイするかもしれないが... • 新規プロジェクトで導入するのは合理的 ◦
長期的にCIのコストを抑制する方向に働く • 実行時間削減のためにFixtureのみの使用に執着するのは本末転倒 ◦ 「それほど手間は違わないから、ここはFactory使わずFixtureにしておきましょう」くらいが良いか 13秒改善のために43コミット・ 77ファイルの変更 (自動生成yamlファイル等含む)
まとめ • Rails Fixturesについて振り返り、実際に運用されているサービスである dev.to のコードでやれる範 囲でFactoryBotによるテストデータ生成を Rails Fixturesで置き換えてみました。 •
テスト実行時間については有意な差が出た(と思う)のですが、積極的に FactoryBotから書き換え るべきかというと... • Factory / Fixture双方の使い方について再度考える良い機会だった。 ◦ 複数のモデルで関連や attributeが異なるデータを多数作るなら Factoryが有利 ◦ AR callbacksの挙動を試す場合、 Factoryを使うほうが楽。 ◦ (テストであっても)件数が多いマスターデータなどの場合は fixtureを使うと良い。 ◦ そもそも永続化しないでテストできる部分は FacotryもFixtureも使わないほうが良い。
御清聴ありがとうございました ご意見・ご質問等あれば懇親会で!
感想 「fixtureで高速化」という点で解決できる問題と、そうでない問題がありそう。 - AR callbacksを使っていて、それがモデルの状態に影響するクラスは厳しい - FactoryBotのcallbackやtransientを使っている場合、置き換えはつらい - specごとに読み込むfixtureを分ける、というアプローチは悪くないかも -
テスト開始時に一度だけ読み込む、というアプローチではないが、それでも AR経由でのレコード保存を減らせる - 予めFactoryBotで記述したデータをfixtureにdumpしてそれをfixtureとして利用できないか? - 上述のspecごとに読み込むfixtureを分けるというアプローチと組み合わせて - テスト実行前に行う、テストデータのキャッシュ生成みたいなもの。
得た学び - FactoryBotは定義の各行を instance_eval しているのでActiveRecordオブジェクトの生成コスト以上に 遅い。 - `fixtures` メソッドを呼び出した段階で、インスタンスが生成される -
`fixtures :users` を呼び出すと、`users.yml` に入っているレコードすべてがインスタンス化される - レコードの多いfixtureを rspec-railsの `config.global_fixture` で使いたい場合は注意。 - `rspec-rails` がところどころ Rails Fixturesをwrapしていて、本体との違いが分かりづらい箇所もある。 - Rails 5.1以降でも `use_transactional_fixtures` を `use_transactional_test` にマッピングしてくれるとか