背景
最近会社の規模が拡大し、障害に対する意識が高まっています。
経営層からも品質に関する話を質問されることが多くなりました。
一方でフロントエンドでは、
- リファクタリング時や機能追加時のデグレが怖い
- E2Eテストは導入されていたが、CI実行時に走らず、メンテナンスもされてないため形骸化していた
といった課題がありました。
フロントエンドで自動テストを本格的に導入した経験はありませんでしたが、
これらの課題を解決するために、自動テストについて考えることにしました。
本記事は、その思考整理の記録です。
目指したい状態
- 開発フローの一部としてテストを書くことが自然に組み込まれている状態
上記だと抽象的なのでもう少し分解してみます
- 機能追加や改修時に、一定の基準でテストが追加される
- CIで自動実行され、テストが落ちた場合は優先的に修正される
- リファクタリングや仕様変更を、過度に恐れることなく進められる
その状態の何が良いか
テストが開発フローに組み込まれると以下のメリットが生まれると思います
リファクタリングや仕様変更の心理的ハードルが下がる
- 壊れていれば検知できるため、変更に対する不安が減る
テスト観点の揺らぎを防げる
- 手動確認では人によって観点がブレるが、コードで担保できる
確認作業の繰り返しコストを減らせる
- 同じ確認を自動化することで、変更のたびにかかる確認時間を抑えられる
なぜ今は開発フローに載っていないのか
なんとなくテストは書いた方がいいよなーとは思っていても、
開発フローに組み込まれていない理由には以下があると考えています。
テストを書く優先順位が明確ではない
- 機能開発や改善が優先される中で、テストは後回しになりやすい
運用ルールが明文化されていない
- どのタイミングで実行するのか
- PR前に必ず実行するのか
- どのコマンドを叩けばよいのか
といった具体的なルールが整理されていない
テストを継続的にメンテナンスする役割が定まっていない
- 誰が責任を持つのかが曖昧で、結果として個人の善意に依存してしまう
そのためテストは「やった方が良いもの」ではあるものの、開発フローの必須要素にはなっていなかったと感じています。
開発フローに載せるためのアプローチ
E2Eは影響範囲が広くメンテナンスコストも高いため、最初から導入するのは難しいと考えました。
そこでまずは、テストを書くこと自体のハードルを下げるため、ユニットテストで雰囲気を掴むところから初めます。
テストフレームワークの選定
テストフレームワークには Vitest を採用しました。
これまでに名前を聞いたことがあったのは Jest でしたが、
以下の理由で選定しました。
- 実行速度が速いこと
- セットアップがシンプルであること(導入コストを下げる)
- ViteベースでNext.jsとの相性が良いこと
特に実行速度は重要だと考えました。
また、自分のサービスではNext.jsを採用しているので、相性が良いという話もあり決めました。
Phase 1: ローカルでユニットテストを実行する
まず、CIに載せずローカルでテストを書いて、書くことに慣れることを優先します。
重要機能の部分を丁度リファクタしようかって話が出たのでデグレ防止のためテストを書きながらリファクタリングを進めることにしました。
- サービスの重要機能である1画面だけユニットテストを書く
- カバレッジは気にしない。上記以外のテストも一旦書かない
- まずはユニットテストってどうやって書くんだろうを覚える期間
- 上記の機能を変更するときは毎回テストを実行してください。とチームに共有する
- 既存のE2Eテストは一旦気にしない
期間:1ヶ月
スコープを限定的に絞ることで、
「とりあえずやってみる」精神で初めてなるべく負荷を下げようと考えています。
Phase 2: テストをCI実行時に走らせる。(上記のリファクタが終わったら)
「書くこと」が習慣化してきたら、CIでの自動実行を必須化します。
ユニットテストだけは実行するようにして、
E2Eに関しては、シナリオを1本決めてローカル実行で回していきます。
ユニットテストをPR時に実行する
- PR作成時にユニットテストを自動実行(GitHub workflowを使用)
- テストが落ちている場合はマージしない
E2Eも一緒に組み込んでしまうと、
- 実行時間が伸びてしまう
- 自分が触っていないところなのにテストが落ちるためマージできない
など、デメリットが多いかなと感じたため一旦はユニットテストだけをCI実行した時に走らせようかなと思います。
カバレッジ目標を設定する
- 目標は75%(コード全体ではなく重要機能だけ)
- 達成そのものよりも「維持すること」を重視する
目標があるとこんなケース書いた方がいいのでは?などの会話に繋がると思ったので決めました。
調べたらGoogleが75%推奨とのことだったので、それに乗っかっています。
https://note.com/tarappo/n/n6edc3355145c
E2Eは最小シナリオから始める
- 重要な購入に関わるフローのみ実装
- APIは基本的にモックせず、実環境に近い形で検証
CIに載せないので、一旦開発環境のAPIをコールする想定でコードを書きます。
まとめ
現在はPhase 1に着手しています。
作業自体は他のメンバーにやってもらい、私はレビューワーとしてコードを確認したのですが以下の気づきがありました。
- この処理の目的は何か?
- どのケースまで担保すべきか?
- 例外系は考慮できているか?
テストを書くことで、この処理って「何を保証したいのか」、どのパターンを考慮しないといけないのかを考えるようになったと感じています。(思考の整理がしやすいなと実感しました!なくてもやれよって話なんですけど・・・)
今後はCI実行時にも走らせるようにして開発フローへの完全に組み込みを目指していきます。
自分のところはこうしてるよーっていう知見があればぜひご教示いただきたいです。
ご覧いただきありがとうございました!
参考
Vitest
GitHubワークフロー