O hirunewani blog

フロントエンドテスト講座を開催した

Created at

フロントエンドテスト講座を行った際の内容をまとめた。

開催理由

開催した組織では、フロントエンドのテストがあまり書かれていない状況だった。

また、歴史的な経緯(後述)により、フロントエンドでテストを書くことに対してネガティブなイメージを持っている人も数名見られた。

とはいえ、テストがあれば気づけていたと思われるバグが本番環境で発生するという事例が直近で複数回発生しており、このままでは歴史を繰り返すか、サービスの信用を失うかのどちらかになると考え実施を提案した。

開催内容

まず、次の内容について説明を行った。

  • 具体的なテストの書き方を学ぶ必要性。
  • テストを書くメリット。
  • フロントエンドのベストプラクティス。

その後、複数回の実技試験を実施した。

具体的な書き方を知らないからテストが書かれない

開催内容を考えるにあたり、どのような内容を提供すれば効果的かを考えた。

テストを書いた方が良いという話は各所でされているにも関わらず、テストが書かれてないということは、テストを書くメリットを伝えるだけでは不十分であることが考えられる。

まずは、テストに対してのネガティブなイメージを払拭する必要があると思い、ネガティブなイメージが持たれる理由を考えた。

結果、具体的な書き方を知らないことがネガティブなイメージの背景にあると推測した。

テストにネガティブなイメージを持つ理由

テストに対して、ネガティブなイメージを持たれても仕方ない現状がある。 例えば、次のような意見が考えられる。

  • 概念の話ばかりで、具体的な書き方が分からない。
  • 何か問題があって、はじめてテストを書く。
  • 既存のテストコードが難解でよく分からない。
  • テストが保守されない。
  • テストがあるメリットよりコストを高く感じる。
  • テストを書くのが面倒。

ネガティブなイメージの背景

  • テストについて漠然と調べると概念の話が多くヒットする。

  • 少し技術力に自信のある人がテストを普及させようとした際に、理想論に偏った概念の話をすることが多い。

    • 優秀であるがゆえに技術にハードルがあると分かっていなかったり、そもそも自分はテストを書かないのに語っている場合がある。
  • テストを書けと指示される状況は、問題が発生してから再発防止策として要求されることが多く見受けられる。

    • 今まで書いておらず十分な知識がないにも関わらず、突然しかも大きな問題を確認するテストを書こうとするため、難解なテストコードが書かれる。

成功体験の欠如

テストの具体的な書き方が分からないことで、次のような悪循環が生まれているのではないと考えた。

  • テストの書き方が分からない。
    • 優秀そうな人に聞いても概念の話しかされない。
  • だからテストを書かない。
  • いずれも大きな問題が発生する。
  • テストを書けと言われる。
  • その大きな問題に対応した難解なテストを頑張って書く。
  • 保守ができず、落ちても原因が分からない。
  • テストを書くメリットを感じられない。
  • だからテストを書かない。

これは開催した組織における歴史的な経緯を元にしている。

このループを断ち切るために、具体的な書き方を教えることが必要だと考えた。

テストを書くメリット

テストを書くメリットは、各所で語られている通りだが、改めて説明を行った。

  • コードの品質を高める。
  • バグを潰す。
  • レビューの負担を軽減する。
  • テストそのものの負担を軽減する。
  • 仕様書の部分的な代替となる。

などが挙げられる。

ただ、実感出来ていなかったり、コストに見合わないように感じる人がいても不思議ではない。

テストを書くメリットを感じるには成功体験が必要

全く書いたことがない人というのは少ないと思われるが、テストを進んで書こうとする人が多数ではない。 ただテストを書くだけでは、テストを書くメリットを感じることは難しい。

まずはテストを書くことを継続して、成功体験を積むことで、はじめてテストを書くメリットを実感することができるということを説明した。

代わりにテストを書かないデメリットを考える

現状テストを書いていない組織であれば、共感しやすいのではないかと思い、考えられるデメリットを提示した。

  • リリース後に問題が発覚する機会の増加。
  • 問題が頻発すれば、ステークホルダーの信用を失う。
  • 調査や修正に追われて開発速度が低下する。
    • 開発時間は増えるが成果は減る。
  • 労働と評価が釣り合わなくなるため、開発者のモチベーション低下。
    • 頑張っているのに認められないという意識が生まれる。
  • サービスの品質低下を招く。

サービスのことを考えるのであれば、テストは書いた方が良い。

テストをシンプルにするためのベストプラクティス 

具体的なテストの書き方を知るために実技講座を行う前に、今回ベストプラクティスとして推奨する書き方を説明した。

ベストプラクティスは、テストをシンプルにするように努力することだ。次の項目は、全てテストをシンプルにするためのベストプラクティスである。

  • テストの対象を絞る。
    • 何をテストしたいのかを明確にする。
  • テストを書くスコープを決める。
    • テストをどこまで書くか決めておかないと、過不足のあるものになる場合がある。
    • 特にテストを書くことが目的になっていると過剰になりがち。
    • なぜそのテストを書くのかを考えられるのであれば、それがベスト。
  • テストの名前は要点を絞って明確に書く。
    • 何をテストするのか。
    • どのような状況なのか。
    • どんな結果を期待しているのか。
  • AAAパターンで構成する。
    • Arrange(準備), Act(動作), Assert(確認)という順でテストを構成すること。
    • テストの可読性が一般的に向上すると言われている。
    • 個人的には、1つのテストで複数の項目を確認するようなテストを抑制できる点も良いと感じている。
  • テストしやすいコードを意識する。
    • テストしやすいコードは、保守性・拡張性・変更容易性に優れている。
    • テスト対象は責任が少なく、無駄な機能がないと書きやすい。
    • テストしやすいか判別できることは、プログラマーとして持っているべき能力だと個人的には考えている。
  • リアルなデータを使う。
    • テストデータはリアルなデータを使うことで、テストの信頼性が向上する。
    • テストデータがリアルであれば、テストが通っても本番で問題が発生する可能性は低くなる。
  • 実装の詳細を避ける。
    • テストは振る舞いを確認するものであり、実装の詳細をテストに含めるべきではない。
    • 内部のロジックではなく、外部から見た振る舞いをテストすることで、リファクタリングが容易になる。
    • あまり考える必要はないと思っているが、テストの再利用性も高まる。
  • ユーザーの視点から操作する。
    • 実装の詳細を避けることと同様に、ユーザーの視点から操作することで、テストの信頼性が向上する。
    • ユーザーが認識できるものを扱う。

これらは、フロントエンド以外に適用できるものもあるが、今回はフロントエンドのみを前提にしている。 また、講座の趣旨として、まずはテストを書いてほしいため、困難なケースやエッジケースを考慮していない。

テストを書くスコープが分からない人へのカバレッジ

なぜそのテストを書くのかを考えられるのであれば、それを元にテストの内容を決めるのがベストだ。

だが、これを考えことが出来ない人も案外いるように感じる。テストで何が出来るのか分からない、テストを書くことが目的になっている、サービスへの理解が不十分などが理由として考えられる。

そういった人に向けて、とりあえずカバレッジ70~80%程度を目指すことを提案した。 明確な基準を設けて、とりあえずテストを書くことでテストに慣れ、どのようなテストを書くべきかを学ぶことができると考えている。

リファクタリングを通してテストについて考える

テストを書くことでリファクタリングが容易になるという話はよくされるが、実際にどうなのか、具体的な例を挙げて説明した。

また、後からテストを書くことは、ほぼリプレイスになることがあり辛いという例示も兼ねていた。まずは新規のコードからテストを書いていこうという提案を行った。

大まかな内容

具体的なコードは省くが、次のような実務で使われていたコードに対してリファクタリングとテストの追加を行った。

  • React RouterのuseParamsをラップしたカスタムフック
    • useParamsの返り値で指定したキーが存在すれば、そのパラメータを返し、存在しない場合にエラーが投げるというもの。
    • 既にテストがあったが、大部分がReact Router自体のテストになっていた。
    • パラメータの取得と、その存在確認に分けることで、テストが容易になるという話をした。
  • Next.jsでフォームを入力するページを丸ごとを対象とした複雑なテスト。
    • 段階を分けて、テストする対象を絞っていった。
    • 最終的に元々テストされていた内容は、フォームに入力されたデータをフォームで使われている関数が意図したように変換しているかどうかであり、mページを丸ごと読み込み複数のダミーを読み込まなくとも簡単に出来るようなテストであることが分かった。
    • テストする対象を絞る、テストするスコープを考えることが重要であるという話をした。

また、テストしやすいコンポーネントの設計についても触れた。一般に推奨されるような記述、例えばNexts.jsのComposition Patternなどは、テストしやすいコードになるという話をした。

実技講座

隔週で課題を配布し、その課題を解いてもらう形式で実技講座を行った。 VitestとPlaywrightが元々利用されていたため、これらを前提に課題を作成した。

次の課題を出した。

  • Vitestを利用したCLIのテスト。
    • そもそもVitestの使い方が分かっていない人が多かったため、Vitestの基本的な使い方を学ぶために選択した。
    • またテスト対象のコードのリファクタリングも課題に含めた。
    • またCLIは入出力など全てをテストしようとすると非常に困難であるため、テストする範囲を絞れるかを図る目的もあった。
      • ここで全てを書こうする人は、テストを書くことが目的になっていると思われ、講座後にテストを書かない可能性が高いと考えた。
  • VitestとReact Testing Libraryを利用したReact Hooksのテスト。
    • React Testing Libraryの使い方を学ぶために選択した。
  • VitestとReact Testing Libraryを利用したReactコンポーネントのテスト。
    • React Testing Libraryの使い方を学ぶために選択した。
  • Playwrightを使ったLargeテスト。
    • Playwrightの使い方を学ぶために選択した。

この講座では、Copilotなどを利用して良いとした。VitestやReact Testing LibraryのAPIを覚えることよりも、どのようなことが出来るのかを知ることと、自分で調べることが重要だと考えたためである。 また、ベストプラクティスを意識してもらうことも重要であり、それが満たせていれば全て書かせても良いと考えた。

そもそもCopilotに雑に書かせると、Jestや少し古いReact Testing Libraryの書き方になることが多く、そのまま利用することは難しそうであった。

実施結果と今後の展望

少なくとも数名からは、進んでテストを書くようになったという話があった。 また直近の組織全体のコードを見ていると、テストが書かれたものや後からでもテストを書いたものが明らかに増加していた。

一方で、テストを一向に書かない人もいるため、今後も継続してテストを書くことの重要性を伝えていく必要があると感じた。