はじめに
ユニークビジョン株式会社では、リードタイムやリリース頻度の改善に取り組んでおり、その一環として手動テストを自動テストに置き換える、という活動をしています。
自動テストのやり方に関する記事は沢山ありますが、どうやって自動テストベースの開発に移行するか、という点で書かれた記事は非常に少ないので、その活動内容と結果をまとめました。
従来の開発フロー
従来の開発フローは、手動テストを前提にしたものでした。手動テストを何度もやるのはオーバーヘッドが大きいので、なるべく複数機能をまとめてリリースするようにしていました。
※ A B C はそれぞれ独立した機能を表しています。
※ 動作確認は、開発が終わったあとにローカルでテストケースをざっと流し、粗いバグを修正するフェーズです。
目指す開発フロー
自動テストを含む適切なレイヤーで動作を保証することを前提とし、機能ごとに細かくリリースする開発フローを目指します。
- 開発フェーズで自動テストまで書くので、従来よりも開発時間は増えます。
- 開発完了時点で自動テストまで書いている状態なので、ある程度動作が保証されます。そのため、動作確認フェーズが無くなります。
- 手動のテストが減るので、テストフェーズは短くなります。
適切なレイヤーでの動作保証とは?
「自動テスト」と何度も書いていますが、正確には「自動テストを含む適切なレイヤーでの動作保証」を増やしていきます。
例として、次のようなものが挙げられます。
- スキーマ定義ファイルから自動生成
- 自動単体テスト
- 型安全
- 宣言的で明確なコード
- E2Eテスト
- 手動テスト
全ての仕様を自動テストで保証することは不可能です。また、ある仕様を保証するのに自動テストが最適とは限りません。
手動テストでしか保証することができない、というケースも少なからずあるはずです。
一つ一つの仕様ごとに最適な方法を組み合わせて保証する必要があります。
何が嬉しいのか
テストの自動化が進み、テストのオーバーヘッドが少なくなると、リリースを細かく区切れるようになります。
認知的負荷の軽減
複数の機能を同時にリリースする場合、単体の機能だけをリリースする場合と比較して
- 機能同士が互いに及ぼす影響を考慮する必要がある
- バグ修正時に確認すべき部分が増える
ため、生産性が低下してしまう可能性が高いです。
リリースの単位を小さくすることで、このような認知的負荷を軽減することができます。
リードタイムの短縮
トータルで同じ工数の機能をリリースしても、リリースが細かい方が、価値 × 時間 の値が大きくなります。
課題
本当に手動のテストを減らしても大丈夫か? というのが最大の心配事です。
この質問にちゃんと答えられない限り、いきなり「自動テストを増やしたので手動テストを減らします」というのはあまりにも暴力的です。
方針
いきなり自動テストベースの開発に移行するのではなく、従来の方法と比較して品質が悪くならないことを確認しながら段階的に移行していきます。 次のような方針を定めました。
- 従来の方法と同じようにテストケースを作成しバグ率を測定する
- ただし、開発者はテストケースを見ないで開発を進める。(開発者だけで考えた動作保証が従来のテストケースと遜色ないものであることを確認するため)
- 過去の同規模の機能開発のテストフェーズでのバグ率と比較する
- 品質に問題が無ければ次回からテストフェーズで範囲を絞ったは手動テストと探索的テストに置き換える
- リリース後バグの数を計測する(プロダクトがスタートしたときから継続して計測し続けている指標)
動作保証の方法の洗い出し
保証する仕組みの導入
元々プロジェクトに存在していたものを含め、次のような仕組みを導入しました。
API 側
- RSpec
- committee で各エンドポイントと OpenAPI の定義に相違がないことを保証する仕組み
フロントエンド側
- 型 (TypeScript)
- Storybook (通常の Story だけでなく Interaction Testing も導入)
- Chromatic による VRT
- openapi-typescript-fetch を用いた OpenAPI から型定義を自動生成する仕組み
個別の仕様の保証
仕様一つ一つに動作保証の方法を書き込んで、どのようなテストを実装すべきかを洗い出しました。下記のようなイメージです。
仕様 | フロント | API |
---|---|---|
権限が無い場合ユーザは投稿を作成できない。 | 無し | 権限がないときに投稿作成が失敗するリクエストスペックを書く |
権限が無い場合登録ボタンは非活性になりツールチップで「権限がありません」と表示する | Storybook で権限があるときとないときの Story を作成する。Story ではツールチップをデフォルトで開いた状態にしておく。 | 無し |
投稿のタイトルは 1 文字以上 100 文字以下とし、投稿ボタン押下時にバリデーションする。 | Zod の Schema 定義で min(1) max(100) とする。(コードが明確なので十分に保証されているとみなす) | モデルスペックで境界値 0, 1, 100, 101 をテストする |
結果
今回の開発と、同規模の開発工数がかかった過去の開発の各メトリクスは以下のようになりました。
従来 | 今回 | |
---|---|---|
開発工数 (h) | 148 | 135 |
動作確認時バグ数 | 70 | 25 |
動作確認時バグ修正工数 (h) | 64 | 20 |
テスト時バグ数 | 12 | 10 |
テスト時バグ修正工数 (h) | 5.45 | 8.60 |
考察(感想?)
今回の開発では、動作確認時のバグ数と修正工数がが大きく減少しました。
仕様ごとにテストコードを考えて、モジュールの開発が終わった時点ではある程度仕様の動作が保証されたことにより、バグが減少されたのではないかと考えています。
一方、テスト時のバグ数はほぼ変わりませんでした。 動作確認時のバグ修正工数が減ったにも関わらず、同程度のバグ数を維持できたので、悪くない結果だと思います。
テスト時バグ修正工数は 3h ほど増加しましたが、全体の開発工数(148h、135h)と比較すると小さい値なので、品質が悪化したとは言えない、と判断しました。
ただし、今回の開発工数にはテストコードの実装分も含んでいるので、同じ開発工数でも従来より機能数はやや少ない可能性があります。
次のステップ
今回の開発で、品質低下は観測されなかったので、今後は下記を実践していきます。
- 動作確認フェーズを廃止する
- テストフェーズでは範囲を絞った手動テストと探索的テストを行う
手動テストをどのように絞っていくか、探索的テストをどのように行うか、という部分は現在ノウハウが少ない状態のなので、調査を進めていきます。(参考になる書籍、記事などありましたら教えていただけると助かります。)
この施策の過程で得られた副次効果
タスクの分割方法の改善
これまで、フロントエンドのタスク分割は、
- デザイン仕様を元にコンポーネント分割しタスク化
- 各コンポーネントに実装すべきロジックを仕様書からピックアップ
というように進めていましたが、今回からは
- 各仕様ごとに「どのように保証するか」を書きだす
- デザイン仕様を元にコンポーネント分割
- 保証内容を各コンポーネントに漏れなく分配
とするようになりました。
このことが動作確認時のバグの大幅削減の1つの要因ではないかと考えています。
単体テストの仕組みを考えるようになった
従来は、テストケースを書く人が「どういうテストでこの仕様を保証するか?」を考えていました。
自動テストベースの方法では、開発者自身が「どういう方法でこの仕様を保証するか?」を考えるようになりました。
テストケースを書いたり実施したりする作業が、アプリケーションの設計や実装に置き換わり、より創造的なものになったと感じています。
最後に
まだまだ、自動テストベースの開発への移行は道半ばです。完全に移行し、品質が上がるまで地道に進めていこうと思います。
進捗があったらまた記事にまとめます!
不要な手動テストを減らしたい!と考えている方の役に立てれば幸いです。ここまで読んでいただきありがとうございました