Slide 1

Slide 1 text

Webフロントエンドの リプレースを支える テストの考え方 @berlysia / JSConf JP 2021

Slide 2

Slide 2 text

誰 - berlysia (べるりしあ、と読む) - Webとアイマスが両本業 - 株式会社ドワンゴ所属 教育事業のWebフロント担当 - Webフロントをやる人 - Webフロントのためにいろいろやる人

Slide 3

Slide 3 text

お品書き 1. 宣伝 2. 誰に向けて話すか 3. 結論 4. ここで扱う「リプレース」とはなにか 5. リプレース時に役立つテストとは何か 6. 実例

Slide 4

Slide 4 text

誰に向けて話すか - これから「リプレース」をやっていこうとしている人へ - 重要な動作を壊さずに仕事をやりとげたと自信をもてるようになりましょう - テストがないところからでも多分何とかなります、実例を置いておくので参考に - 使えるものは何でもつかうために、どんなテストが役立つのかみていきましょう - リプレースに飛び込む前にやるべきことも示しておきます - すでに「リプレース」をやりとげた人へ - ここでは「それはそう」みたいなことをたくさん言います - こういう整理の仕方をしたんだなとか思ってもらえたら嬉しいです - こういう状況でこうして乗り切ったよ、みたいな事例をぜひ教えてください - 上記を含むすべての人へ - 多くの人にとっては普通にテストを書く上でも役に立つことが含まれていると思います

Slide 5

Slide 5 text

結論 Webフロントエンドのリプレース時のテストは シナリオベースで画面操作を想定した結合テスト に注目すべし なぜなら: 1. (ここを) 2. (うめていきます)

Slide 6

Slide 6 text

「リプレース」❓

Slide 7

Slide 7 text

よくある「リプレース」 - ○○(歴史あるライブラリ・フレームワーク)で書かれているものから ××(最近話題のライブラリ・フレームワーク)に載せ替える - ○○(サーバーサイドメインの WAF)の view にべったりなフロントエンド実装をひっぺ がす - etc...

Slide 8

Slide 8 text

周辺の言葉 『レガシーソフトウェア改善ガイド』 曰く: - リファクタリング →コードの構造をメソッドやクラスのレベルで変更する - リアーキテクティング →モジュールやコンポーネントのレベルで行うリファクタリング - リライト →可能な限り高いレベルで行うリファクタリング - ビッグ・リライト - 全てを一気に書き直し、置き換える - インクリメンタルなリライト - 小さい単位でリライト、徐々に置き換える - 進め方によってはリアーキテクティングに限りなく近づく - リプレース →サードパーティのソリューションに置き換える

Slide 9

Slide 9 text

周辺の言葉 『レガシーソフトウェア改善ガイド』によれば…… - リファクタリング →コードの構造をメソッドやクラスのレベルで変更する - リアーキテクティング →モジュールやコンポーネントのレベルで行うリファクタリング - リライト →可能な限り高いレベルで行うリファクタリング - ビッグ・リライト - 全てを一気に書き直し、置き換える - インクリメンタルなリライト - 小さい単位でリライト、徐々に置き換える - 進め方によってはリアーキテクティングに限りなく近づく - リプレース←これの話ではなさそう →サードパーティのソリューションに置き換える

Slide 10

Slide 10 text

最近のWebフロントエンドの「リプレース」 - コンポーネント単位で○○を導入、徐々に全体に広げました →リアーキテクティング的なリライト - ルーティング・画面単位で○○に置き換えていきました →インクリメンタルなリライト - 全部一気に書き換えました👊 →ビッグ・リライト 「リプレース」とは、おおむねリライトのことと言えそう 少なくともリファクタリングの一種ではありそう

Slide 11

Slide 11 text

「リプレース」≒ リライト 「リプレース」 ⊂ リファクタリング ということにして、今後は「」を外して呼ぶことにする ここでは、『レガシーソフトウェア改善ガイド』の言葉のもとで……

Slide 12

Slide 12 text

ところで:そのリプレース、機は熟しているか 『レガシーソフトウェア改善ガイド』曰く、リライトは難しい - すべての新規開発時におこるリスクをふたたび負うことになる - 作ったものが役に立たないかも・性能要求を満たさないかも - 新規に立ち上げるオーバーヘッドを軽視しやすい - 開発期間が長くなりやすく、オーバーヘッドの甘い評価以上に、見積もりよりも時間 がかかりやすい - そもそも仕様が複雑だったことが原因なら書き直しても難しいままになる

Slide 13

Slide 13 text

リプレースを選ぶ条件 『レガシーソフトウェア改善ガイド』曰く、次のふたつを満たすとき: 1. リファクタリングを既に試みて失敗している - 必ず最初にリファクタリングを試みて、どれくらい効くか確かめる 顕著な改善が得られないとわかってからリライトを考え始める - コードに十分詳しくなってからの方が、リライト後へのよい洞察が得られる - 調査的リファクタリング 2. パラダイムシフトを取り込みたい - 人材事情でのリライト、より軽量なWAFへのリライトなどが例示 - 宣言的なUI記述を中心にした思想は、これにあたるかも? - WAFべったりから独立したいというのも、これにあたるかも?

Slide 14

Slide 14 text

再掲)よくある「リプレース」 - ○○(歴史あるライブラリ・フレームワーク)で書かれているものから ××(最近話題のライブラリ・フレームワーク)に載せ替える - ○○(サーバーサイドメインの WAF)の view にべったりなフロントエンド実装をひっぺ がす - etc... ライブラリ名やフレームワーク名に目が行きがち その選択に至る原点の「つらさ」は多種多様

Slide 15

Slide 15 text

よくあるつらさ - いまの開発規模に合っていない - いまの開発体制に合っていない - 世の中のデファクトから意図せず外れている - 採用の問題 - 新しい道具に乗りづらい問題 - 安心して修正・拡張できない - ライブラリ更新がこわい - パフォーマンス改善の前にやることがいっぱい - 見通しが悪くて変更が難しい - 経緯を知っている人がいなくなって手探り - etc...

Slide 16

Slide 16 text

よくあるつらさ - いまの開発規模に合っていない - いまの開発体制に合っていない - 世の中のデファクトから意図せず外れている - 採用の問題 - 新しい道具に乗りづらい問題 - 安心して修正・拡張できない - ライブラリ更新がこわい - パフォーマンス改善の前にやることがいっぱい - 見通しが悪くて変更が難しい - 経緯を知っている人がいなくなって手探り - etc... その問題が解けるように よく計画しましょう リアーキテクティングくらいに 落とせるかも まず何をやっているか 調べつくした方が良い 上側のつらさに分解しよう その過程でだいぶ綺麗になるかも

Slide 17

Slide 17 text

再掲)リプレースを選ぶ条件 『レガシーソフトウェア改善ガイド』曰く、次のふたつを満たすとき: 1. リファクタリングを既に試みて失敗している - どうにかして手ごろなリファクタリングに落とせないか、まず試す - 既存コードに十分詳しくなってからでも遅くない - 調査的リファクタリング 2. パラダイムシフトを取り込みたい - 人材事情でのリライト、より軽量なWAFへのリライトなどが例示 - 宣言的なUI記述を中心にした思想は、これにあたるかも?

Slide 18

Slide 18 text

再掲)結論 Webフロントエンドのリプレース時のテストは シナリオベースで画面操作を想定した結合テスト に注目すべし なぜなら: 1. リプレースとは、ふつうのリファクタリングには既に失敗していて、パラダイムシフト を乗り越えるために、新たに書き直すことだから。 2. (つぎはここをうめます)

Slide 19

Slide 19 text

「リファクタリング」❓ ところで

Slide 20

Slide 20 text

リファクタリングに欠かせないもの 『リファクタリング(第2版)』によれば: 「外部から見たときの振る舞いを保」つために、テストが不可欠 リファクタリング(名詞) 外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部 構造を変化させること。 リファクタリングの第一歩 リファクタリングを行うとき、最初にすることは常に同じです。対象となるコードについてきちんと したテスト群を作り上げることです。テストは不可欠です。(略

Slide 21

Slide 21 text

1. ない 2. 役に立たない 3. 書きづらい リプレースしたいコードのテストにありがちなこと

Slide 22

Slide 22 text

リプレースではどんなテストが欲しいのか リファクタリングのお伴になる 十分素早く実行できるテスト 置き換えの前後で動作を保つことを 検証できるようなテスト もしテストが不十分なときは 誰でも比較的容易に作れるテスト そこまで多くない数でも 機能の信頼度を高めてくれるテスト ? ? ? ?

Slide 23

Slide 23 text

テストの分類いろいろ - 工程による分類 - ex) 単体テスト・結合テスト・システムテスト・受け入れテスト - 実行方法による分類 - ex) 動的テスト・静的テスト - テスト技法による分類 - ex) ブラックボックステスト・ホワイトボックステスト - 結合度による分類 - ex) 単体テスト・○○結合テスト・E2Eテスト - 依存するリソースと実行時間による分類(Test Sizes) - ex) スモール・ミディアム・ラージ

Slide 24

Slide 24 text

よく見る図:The Test Pyramid - 図に書いてあること - - この図にいま学ぶべきことは - この図を読むときの注意 - もしより上位のテストが十分に高速で メンテナンスが容易であるならば 低レベルのテストは省いてよい The Practical Test Pyramid TestPyramid より下位のテストは実行速度が速く メンテコストも低い 結合度の高いテストはより少ない数でも 効果を発揮する

Slide 25

Slide 25 text

再掲)リプレースではどんなテストが欲しいのか リファクタリングのお伴になる 十分素早く実行できるテスト 置き換えの前後で動作を保つことを 検証できるようなテスト もしテストが不十分なときは 誰でも比較的容易に作れるテスト そこまで多くない数でも 機能の信頼度を高めてくれるテスト 結合度が低いテストは 実行速度が速い ? ? 結合度が高いテストは 少なくても効果を発揮する

Slide 26

Slide 26 text

私たちはこれから実装を置き換える 新 旧 旧 外側 外側

Slide 27

Slide 27 text

1. E2Eなテスト 旧 旧 外側

Slide 28

Slide 28 text

2. 置き換える単位への結合テスト 旧 旧

Slide 29

Slide 29 text

3. 置き換える単位より内側のテスト 旧

Slide 30

Slide 30 text

4. 置き換える単位へのホワイトボックステスト 旧 モック

Slide 31

Slide 31 text

再掲)私たちはこれから実装を置き換える 新 旧 旧 外側 外側

Slide 32

Slide 32 text

役に立つテストを全て選べ

Slide 33

Slide 33 text

役に立つテストを全て選べ

Slide 34

Slide 34 text

役に立つテストを全て選べ 置き換える単位より外側の ブラックボックステストが欲しい

Slide 35

Slide 35 text

再掲)リプレースではどんなテストが欲しいのか リファクタリングのお伴になる 十分素早く実行できるテスト 置き換えの前後で動作を保つことを 検証できるようなテスト もしテストが不十分なときは 誰でも比較的容易に作れるテスト そこまで多くない数でも 機能の信頼度を高めてくれるテスト 結合度が低いテストは 実行速度が速い 置き換える単位より結合度が上の ブラックボックステストが良い ? 結合度が高いテストは 少なくても効果を発揮する

Slide 36

Slide 36 text

図:The Testing Trophy from Static vs Unit vs Integration vs E2E Testing for Frontend Apps - より下位がコスト低・高速なのは同じ - より上位のテストは より大きい問題に関心がある - 障害点が多く壊れやすい? →より多くのコードをテストしているから - エッジケースはより下位のテストで拾う より上位の実際の使い方に近いテストは より多くの信頼度をもたらしてくれる 統合テストは信頼度とコスト・速度の バランスが良いので多くするとよい

Slide 37

Slide 37 text

再掲)結論 Webフロントエンドのリプレース時のテストは シナリオベースで画面操作を想定した結合テスト に注目すべし なぜなら: 1. リプレースとは、ふつうのリファクタリングには既に失敗していて、パラダイムシフト を乗り越えるために、新たに書き直すことだから。 2. リプレースはリファクタリングの一種なので、書き直しの前後で変わらず信頼できる テストが、手ごろな速度で実行できるべきだから。

Slide 38

Slide 38 text

1. ない 2. 役に立たない 3. 書きづらい 再掲)リプレースしたいコードのテストにありがちなこと

Slide 39

Slide 39 text

再掲)結論 Webフロントエンドのリプレース時のテストは シナリオベースで画面操作を想定した結合テスト に注目すべし なぜなら: 1. リプレースとは、ふつうのリファクタリングには既に失敗していて、パラダイムシフト を乗り越えるために、新たに書き直すことだから。 2. リプレースはリファクタリングの一種なので、書き直しの前後で変わらず信頼できる テストが、手ごろな速度で実行できるべきだから。

Slide 40

Slide 40 text

再掲)リプレースではどんなテストが欲しいのか リファクタリングのお伴になる 十分素早く実行できるテスト 置き換えの前後で動作を保つことを 検証できるようなテスト もしテストが不十分なときは 誰でも比較的容易に作れるテスト そこまで多くない数でも 機能の信頼度を高めてくれるテスト 結合度が低いテストは 実行速度が速い 置き換える単位より結合度が上の ブラックボックステストが良い ? 結合度が高いテストは 少なくても効果を発揮する

Slide 41

Slide 41 text

例えばこういうのでいい

Slide 42

Slide 42 text

スナップショットを撮ってもいい/メンテは大変

Slide 43

Slide 43 text

スナップショットを撮ってもいい/メンテは大変 リプレースのために必要な信頼度を 一時的にでも稼ぐようにしよう

Slide 44

Slide 44 text

実例@N予備校の教材フロントエンド

Slide 45

Slide 45 text

どういう実装か - Railsのviewの結果に対してJSが動作している状況 - 規約で縛られたHTMLと教材のメタデータが与えられており、JS実装はjQueryで DOMを書き換えて、動作を与える - ベースになるHTMLは一定の規約に則っているくらいで、教材ごとに異なる

Slide 46

Slide 46 text

何がつらかったか 調査的リファクタリングの結果わかったことは: - JS実装の見通しが悪い - 関連する実装の凝集度が低い - 各所で機能の有無が論理的凝集によって隠れており、どこで何をサポートすべきか見づらい - CSS実装の見通しが悪い - 歴史的経緯が残りっぱなしの SCSSがそのまま残っている - 要素セレクタや子セレクタが濫用されていて変更に著しく弱い - 外観用の class と動作用の class が一部共通になっていて CSS を不用意にいじれない - テスト - ほぼない - 抵抗を示すような単体テストが地道に書かれていた

Slide 47

Slide 47 text

リスク検討 壊すとどうなるか - 利用いただいている方々へのご迷惑、はもちろんのこと…… - 2万人のN高・S高生が単位を取るための学習を進められなくなる - 期末試験の時期にはとくに紙試験へのフォールバックや再調整などで現場を泣かせる パフォーマンス - ログイン前提なのでSEO観点は除外できる - 比較的高頻度にロードされるのでリーズナブルな範囲で求められる - バンドルサイズはほぼ問題にならないスケール

Slide 48

Slide 48 text

リプレースの方針 まず、調査的リファクタリングの結果を解消するリファクタリングを行う - DOM周りで関連するコードの凝集度を上げる - 制御結合をやめ論理的凝集を解消、呼び出し側が命じる作りに統一する - 実装外部との結合面を整理し、滑らかにする その後、実装を画面ごとに置き換える - あとは各個撃破👊 - ここには別にドラゴンが潜んでいる

Slide 49

Slide 49 text

テストの方針 - シナリオテストで教材の一連の操作を検証したい - 進行不可系の不具合は絶対に出したくなかった - CSSには基本的に干渉せず、classも変更しないことを前提にする - 外観崩れの検証はDOMレベルで一致していればよいとわかる - 構造レベルでは書き換えてしまうのでより上位のテストが必要 - 単体テストは書けなかった

Slide 50

Slide 50 text

私たちはこうして無からテストを作った 1. jestでjsdomを使ってDOMが扱える状態を作る 2. ユーザーの操作を模してtesting-libraryで操作する 3. 操作前後の特徴になる部分でDOMのスナップショットを撮る 4. 一連のDOMスナップショットの連なりでシナリオテストを構成する

Slide 51

Slide 51 text

再掲)スナップショットを撮ってもいい/メンテは大変

Slide 52

Slide 52 text

再掲)結論 Webフロントエンドのリプレース時のテストは シナリオベースで画面操作を想定した結合テスト に注目すべし なぜなら: 1. リプレースとは、ふつうのリファクタリングには既に失敗していて、パラダイムシフト を乗り越えるために、新たに書き直すことだから。 2. リプレースはリファクタリングの一種なので、書き直しの前後で変わらず信頼できる テストが、手ごろな速度で実行できるべきだから。