チーム開発を加速するテストの育て方
テストを書いてないというチームには色々理由があると思いますが、「何をテストすべきかわからない」「書き方がわからない」「どのくらいメリットがあるかわからない」という意見は多いのではないでしょうか?テスティングフレームワークの選定や使い方を学ぶのは重要ですが、それ以上にテストの目的や戦略を学ぶことが重要です。チーム開発においてテストを活かすのは相応の知識とスキルが必要になりますが、活かせればテストは開発スピードを維持・促進する飛び道具になり得ます。
本稿は筆者が取り組んで実際に高いチーム満足度と速度を得られた、テスト戦略についてまとめたものです。
補足:「単体テスト」の定義
本稿では「単体テストの考え方/使い方」に沿って単一プロセスで実行可能なテストのことを単体テストと呼称しています。
ただし、「単体テスト」の定義にはさまざまな流派があります。以下は著書 リファクタリングなどで有名なマーティン・ファウラーの発言です。
“in the first morning of my training course I cover 24 different definitions of unit test”.
“トレーニング コースの最初の朝、私は単体テストの 24 の異なる定義をカバーしました。“
それほどに「単体テスト」の指す言葉の定義は人によりけりで、異なります。フロントエンドではAPIなどの外部システムとの「統合」があればインテグレーションテストと呼ぶ人もいるし、複数のモジュールからなるもののテストをインテグレーションテストと呼ぶ人もいます。
単体テストをどう定義するかについては、プロジェクトやチームによってそれぞれで良いと筆者は考えます。呼称や定義の統一は重要ですが、世間的にさまざまな解釈がある以上プロジェクト内において統一できていることが重要です。
テストの目的
新規開発か運用か、開発規模の大小など様々あれど、ビジネスにおいて開発速度というのは常に求められるものです。多くのWebアプリケーションは作って終わりではなく、その後も機能追加や修正などさまざまな運用をし続けることになります。初期開発においても、仕様変更・追加要件・バグ修正はほぼ確実に発生します。このような多くの変更に耐えうる変更容易性が中長期のみならず短期的にも必要になってきます。変更容易性を損なうと、開発プロジェクトはたちまち遅延しはじめることでしょう。
プロダクションコードの変更によって起こるリグレッション(退行、予期せぬ変更)は、テストによって検知することが可能です。テストによって、プロダクションコードの変更が与えた影響が実装者以外にも明確になるので、調査や考慮漏れに伴う工数が削減されます。また、予期せぬ変更も明確になるのでバグ対応工数も削減できます。これらの工数は決して無視できるものではありません。これらが原因で遅れた開発プロジェクトは非常に多く存在します。
開発速度を維持すること、そして開発速度を悪化させうるリグレッションを防ぐことこそが、テストを書く大きなモチベーションです。
テストを書くことは目的ではない
よくあるアンチパターンですが、テストを書くことが目的になってしまうことがあります。「テストを書くといいらしい、だからテストを書こう」と言ったものです。テストを書くことは目的ではありません。テストを書くことは、目的を達成するための手段の一つです。カバレッジを無理に100%にしてリグレッション検知の役には立たないようなテストを書いてしまってたり、書き方に悩んで時間を使いすぎて開発速度を損なうようなら、目的に反しています。
リグレッション検知に役立たないテストは避けるべきですし、書き方やテスティングフレームワークの使い方で時間がかかるようならチームメンバーに相談すべきです。あまりに時間がかかるようなら、時には諦めることも重要です。
テストが効果的なプロジェクト
テストの目的を意識すると、テストが効果的な場合とそうでない場合があることに気づけます。フロントエンドで言うと、振る舞いのほぼないランディングページを量産するプロジェクトにおいては単体テストを書いてもあまり役に立たないかもしれませんが、Visual Regression Testing(VRT)は大いに役立つことでしょう。一方でいわゆるWebアプリケーションとよばれるものの多くでは、単体テストもVRTは非常に有用になり得ますが、E2Eテストに投資しすぎると膨大な工数を消費することになるでしょう。
テスト運用の課題と対策
目的を意識できても、目的を達成するようなテスト運用をすることは難しいことです。特に躓きやすい点として、以下の課題が挙げられます。
- テストへの投資幅
- テスティングフレームワーク知識
- テスト設計
- 設計・書き方などのチーム内統一
- ドキュメントと合意形成
これらの詳細と対策についてみていきましょう。
テストへの投資幅
テストと一言に言っても、単体テスト・VRT・E2Eなど多様なテストが存在します。どのテストで何を目指し、どのくらい工数をかけるべきかのバランスを決めておかないと、目的に反して開発速度を悪化させてしまいます。
より上位のテストであるE2Eなどはリグレッション検知における忠実性は高いがコストも高く、速度・安定性に欠けます。この点から考えられた粒度ごとの最適なバランスの図として有名なのがテストピラミッドやテスティングトロフィーです。
テストピラミッド(抜粋)
テスティングトロフィー(抜粋)
フロントエンド開発では後者のテスティングトロフィーの方が適してると言われることも多いので、筆者は中間レベルに該当するAPI通信を伴うコンポーネントや処理に対しては厚くテストし、低レベルなUIコンポーネントのテストや高レベルなE2Eテストは薄くテストしていくことにしています。
テスティングフレームワーク知識
チームで運用する以上、採用するテスティングフレームワークについて1人以上のリードエンジニアが必要になります。テストの導入を考えているが自信がないという方は公式ドキュメントなどをしっかり読みこみ、ある程度使い方や環境構築については習得しておく必要があります。
フロントエンドの場合、フロントエンド開発のためのテスト入門はテスティングフレームワークの使い方から後述のテスト設計・戦略まで網羅的・効率的に学べるので、お勧めです。
テスト設計
テスト設計は目的である開発速度を維持することやリグレッションを防ぐ上で、最も重要なポイントです。どのテストでどこまで担保し、何をしないのか、何をモックにするのか、テスト粒度はどうやって揃えるのかなど、テスト設計として考えることは多岐にわたります。幸い、これらは書籍などから体系的に学ぶことが可能です。以下はテスト設計について学ぶにあたり、筆者がお勧めしたい書籍です。
上記はどれもお勧めですが、あくまでテストについて学びたいならテスト駆動開発と単体テストの考え方/使い方がお勧めです。プロダクションコードの設計やエンジニアリングという仕事に対する考え方など幅広く参考になるのがClean Craftsmanshipです。
「単体テストの考え方/使い方」に関しては筆者の以前書いた記事でフロントエンド向けに要約しているので、こちらも参考になるかと思います。
設計・書き方などのチーム内統一
少数のリードするエンジニアが設計や書き方を学んでも、これらをチームで運用するには全員が一定以上の理解度やスキルを習得する必要があります。テストに限らず、チームに設計や書き方を共有することはそれ自体が難しいことです。
筆者はモブプログラミング(もしくはペアプログラミング)でこれらの課題を解決してきた経験が多いので、これを推奨します。モブプログラミングではお互いに苦手領域をフォローしあったり、観点漏れをリアルタイムに指摘しあえます。
モブプログラミングの採用検討時に、「効率が悪い」という指摘を耳にすることがありますが、これは効率と言う言葉について解像度を高く議論する必要があります。具体的には、効率にはリソース効率とフロー効率が存在し、モブプログラミングはリソース効率的に悪くフロー効率的に良い傾向にあります。昨今の研究では、フロー効率の指標であるリードタイムが最も重要な開発速度の指標の1つに数えられており、これらからチームメンバーのタスクが埋まっているかよりリードタイムを重視している筆者にとっては、モブプログラミングは効率が良いと言えます。
ドキュメントと合意形成
テストを運用する上で、目的・設計・サンプルなどが記載されたドキュメントを用意して、いつでも見返せるようにしておくことはとても重要です。ドキュメントはチームにおける合意形成にもなりますし、迷った時の道標にもなります。
一方で、用意したドキュメントに対し以下のような経験もしくはフィードバックを受けた方は多いのではないでしょうか?少なくとも筆者はどれも大いに心当たりがあります。
- 読んでない
- 読んだが理解できない
- 理解できたが使えない
- 使えるけど応用はできない
テストのドキュメントには前述の通り目的・設計・サンプルなどの最も重要なことのみを記載し、さらにこれを口頭でも共有することで、少なくとも上記の「理解できた」までは目指せると思います。ドキュメントの運用は一朝一夕で仕上げることはできないので、まずはシンプルかつライトに始められるところから初めて、チームにあった運用を目指していくが良いと思います。
段階的にチームに根付かせる
課題と対策が明確になったので、次にチームでテスト運用が回り始めるまでの計画について考える必要があります。新規プロジェクトでテストを運用する、もしくはテストを書いてなかったチームでテストを運用し始めるというのは大きな変化です。変化は急すぎるとチームがついていけず、導入に失敗しがちなので、丁寧に段階を追ってチームに根付かせることを目指します。
実際に筆者が行ったステップは以下です。導入ステップを考え始めたのが、テストの理解を深めるべく前述の書籍などを読んだり識者に相談したりした後だったので、これをstep0とおいています。
- step0: テスト関連の書籍を読む、識者に相談し理解を深める
- step1: 投資すべきポイントと計画を考える
- step2: ドキュメントの作成と共有
- step3: 参考になる例を実装する
- step4: 共有・相談・議論の場を整える
おおむねどのプロジェクトでも上記の流れは参考になると思います。以降は筆者の体験を踏まえ、上記のstepについて詳解します。
1.投資すべきポイントと計画を考える
前述の#テストへの投資幅で述べた通り、テスティングトロフィーに乗っ取ったバランスは費用対効果が高く、中間層のテストへの投資は効果的です。
筆者が担当するプロジェクトは連携先APIが複数なことが多く高い調整コストがかかるため、E2Eはある程度完成してから徐々に作って行った方が効率的と判断することが多いです。ただプロジェクト特性にもよりけりなので、インシデントにシビアなプロジェクトの場合には「コストが高くてもリグレッション検知を最大限優先してE2Eにも一定投資する」という判断が妥当な場合もあります。
フロントエンドにおけるテスティングピラミッドは、Unitがstorybook、IntegrationはAPI通信を伴うコンポーネントや処理のjest(vitest)と筆者は位置付けて考えています。低レベルなコンポーネント(atomic designで言うatomコンポーネントなど)のjestは多くの場合あまりリグレッション検査の役に立たないので、筆者はほとんど書きません。これらから筆者が担当していたプロジェクトでは、投資すべきポイントを以下のように定義しました。
- storybook、jestでリグレッション検知を整えて開発速度を維持する。
- storybookはUIパターンの網羅を目指す。ただしstoryを増やしすぎないこと。
- jestでは主にAPI通信を伴うコンポーネントや複雑な処理を検証する。
- 取るに足らないテストと判断できるものは排除する。
2.ドキュメントの作成と共有
次に前述のポイントを意識しつつ、目的・設計・サンプルをドキュメント化します。ドキュメントはシンプルで読むのが辛くない量を目指さないと読むハードルが上がってしまうので気をつけます。以下の記事はチーム向けの意味も込めて書いたのですが、長いのでこれをさらに要約します。
上記記事の内容に基づき、以下のような構成のマークダウンファイルを用意しました。
## 単体テストの目的
Jest による単体テストを用いて、以下を達成することを目指す。
- リグレッション耐性: 修正による意図せぬデグレを防ぐ
- リファクタリング耐性: Flaky や壊れやすいテストを避ける
- 迅速なフィードバック: 高速なテスト実行
- テスト自体の保守性: テストコードの読みやすさ
## テスト観点
### コンポーネントのテスト観点
...
### 外部依存の取り扱い
...
### submit時の送信データの検証
...
## テストケースの記述
### コンポーネントのテスト
...
### テストフェーズの記載(AAAパターン)
...
### テストの共通化
...
実際にはサンプルコード込みで3000文字程度だったので、これなら思い返したい時に読める量と判断しました。ただ最初に読むきっかけがなければやはりドキュメントは読まれません。筆者は上記をほぼ読み上げるだけ+QAの会議を設定し、口頭でもチームに上記ドキュメントの内容を共有しました。ここで意見があれば議論し、取り込むことも重要です。
3.参考になる例を実装する
ここまででチームへの接続・合意形成は完了していますが、現実にテストを運用していくにはサンプルだけでは不十分です。テストに限らず、既存コードというのは実装者に大きな影響を与えます。コピペされることは往々にしてあるでしょう。コピペもととして適切なサンプルが1つあるだけで、運用ハードルは一気に下がるでしょう。
#2.ドキュメントの作成と共有で作成するサンプルはわかりやすさ重視で実際のプロダクションコードにはないテストで構いません。一方でここで書く参考実装はリポジトリにコミットされるコードです。チームで最初にテストを書いてコミットするのは心理的敷居の高い行為であり、リードするエンジニアが率先して行うべきです。
4.共有・相談・議論の場を整える
ここまででチームメンバー全員が同じようにテストを書けるようになったでしょうか?まだです。ドキュメントや参考実装はあくまで補助的であり、チームメンバー全員のナレッジを突然底上げしてくれるような魔法ではありません。
モブプログラミングはお互いのナレッジや観点を交換しあえる魔法です。前述の通り、これによりフロー効率も格段に上がります。ジュニアエンジニアもシニアエンジニアもお互いに学びがある、メリットを数えるとやらない理由はないくらい効果的です。
ただし10人20人でモブプログラミングしてたらリソース効率が悪すぎるし、そもそも議論の収集が大変になってきます。そしてモブプログラミングはとても、とても疲れます。適宜休憩も必要だし、慣れない内は1日1時間くらいから少しづつ始めることをお勧めします。
モブプログラミングやレビューの場で徐々にチーム内に考え方や書き方が浸透することで、チームにいいテストを書こうという文化が根づきます。
チームからの評価
筆者は上記のステップに基づいて、実際に2つのチームでテスト運用の導入を行いました。同じチームでテストを書かなかった場合の速度と比較しないと定量的な比較はできないため定性的な評価になってしまいますが、現在これらのチームでは効果的・効率的なテスト運用ができている状態に至ってると筆者は感じています。
これが筆者の独りよがりの感想である可能性もあるので、匿名アンケートという形でチームメンバーからフィードバックを集めました。アンケート自体はテストに限らず、技術選定や進め方含めて質問を作成しました。筆者としては耳が痛い話もあったので、それなりに本音で回答してもらえたと思ってます。その中でもテストに限れば、全体的に満足度が高い状態なことがわかりました。「手戻りが減った」「苦手意識がなくなった」「テストの分量がちょうどよかった」など、嬉しい声は多かった一方でネガティブな意見はみられませんでした。
また筆者が予期していなかった嬉しい誤算で、エラー時UIの実装漏れがかなり減ったように感じます。筆者の経験上、エラー時UIはとても実装漏れが発生しやすいのですが、テストを書いてると観点としてほぼ必ず出てくるので、プルリクエスト時の指摘や手戻りが大きく減ったように感じています。
まとめ
筆者の経験上はテスト運用に課題を抱えているチームは多かったので、今回チーム全体で高い満足度を得られるにいたったことはとても嬉しく思います。一方でまだまだこの記事に示した内容にはブラッシュアップの余地があるとも感じています。プロジェクト規模にもよるし、チームメンバーのスキルの高さに依存してうまくいった部分は多々あるとも感じています。
今後も同様の活動を続けて、より良いテストの文化の育て方を見出していきたいと思います。
この記事が参考になれば幸いですが、不明・不安な点などあればぜひX(Twitter)などからご相談ください。
Discussion