Article by: Artem Zakharchenko
この記事は、JavaScript用のAPIモックライブラリ「MSWJS」の作成者であるArtem Zakharchenkoによるゲスト投稿です。彼はEpicWebや自身のブログでも、テストに関する記事を執筆しています。
テストの不安定さ(フレーク性)は大きな課題です。
検出して修正するには膨大な時間がかかるだけでなく、テストの最も重要な価値である「信頼性」を損なう原因にもなります。信頼できないテストは、存在する意味がありません。そのメンテナンスに費やす時間も、結果的には無駄となってしまい、本来なら開発に充てられたはずの貴重な時間までも奪ってしまいます。
私はこれまでに、フレークテストへの対処法をご紹介してきましたが、今回はその根本原因である「不安定なテスト(フレークテスト)をいかに見つけるか」についてお話ししていきます。
フレークテストの根本原因
テストを重ねる中で学んだことの一つに、「テストの不安定さはシステムのあらゆる層に潜んでいる」ということです。テストの書き方、セットアップの方法、使用しているツール、さらにはそれらを実行するハードウェアに至るまで、どの部分にも問題の種は潜んでいます。
エンドツーエンド(以降、E2Eと表記)テストが特に不安定だと言われるのは、決して偶然ではありません。システムの端から端まで、多くの要素が絡み合うため、どこで問題が発生してもおかしくありません。それに加えて、E2Eテストはメンテナンスコストが高く、チームの誰もが対処をためらうような厄介な存在になることも多くあります。
さらに厄介なのは、フレークテストの「たまに失敗する」という性質が、問題の深刻さを見落としやすくすることです。複数の層が関わっていると、「たまたまどこかで問題が起きただけ」と片付けてしまいがちです。「再実行」ボタンを押してテストが通れば、それで良しとしてしまうことも少なくありません。
残念ながら、ほとんどのチームは初期のフレークテストをこうして放置します。
一度無視し、二度無視し、気づけばCIの結果に一喜一憂しながら、テストが緑になるまで再実行を繰り返す。そんな状況に陥ってしまうのです。
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
E2Eテスト自体を諦めてしまったチームと話したことがあります。最終的に、テストスイートの信頼性が失われてしまったことが原因でした。
しかし、本来の自動テストはそうあるべきではありません。
フレークテストに対処する方法
大規模なチームでは、特にフレークテストの問題が見過ごされることがあります。
そもそも、自分がそのテストの不安定さに直面していないかもしれませんし、何度も再実行する必要がない場合もあります。影響を受けているチームメンバーも、「仕方がないもの」と受け入れてしまうことが少なくありません。
しかし、そんなテストのフレーク性は最終的にチーム全体の生産性を低下させます。
重要なプルリクエストが、ただテストが通るまで再実行を繰り返す「リトライゲーム」のせいで滞ってしまうのは、非常に厄介です。
私自身も、そうした状況に何度も直面してきました。
だからこそ、私はビルドを常に安定させ、フレーク性を見つけ次第、徹底的に排除することに注力しています。
そのためにも、フレークテストを検出し、追跡できるツールを活用するのは非常に有効です。
最近、CodecovがTest Analyticsを発表したので、早速試してみることにしました。最も一般的なフレークテストの原因を意図的に再現し、それをどのように検出・修正できるのかを検証するのが、一番の近道だからです。
フレークテストの検出を検証する
ここでは、Vitestのブラウザモードを使用して、シンプルなブラウザ内テストを実施します。
今回テストするのは、ユーザーの投稿リストをレンダリングする<App />コンポーネントです。
かなりシンプルな内容ですが、鋭い方なら既に問題に気づいているかもしれません。
しかし、もし私がそれに気づかなかったとしたら、どうでしょうか?
私はこのテストを書き、実行し、成功を確認し、変更を承認しました。
そして今、このテストはチーム全員のメインブランチで実行される状態になっています。
ここで、テスト分析を導入してフレークテストを追跡すると、どのような影響があるのかを見てみましょう。
CodecovのTest Analyticsを導入する
Test Analyticsは Codecov の一部として提供されており、フレークテストの検出機能も備えています。
公開リポジトリに対しては無料で利用でき、プライベートリポジトリでは Pro またはEnterprise プランが必要です。
プロジェクトへの統合はわずか3ステップで完了します。
ステップ1:GitHubにCodecovアプリをインストール
まず最初に、GitHubにCodecovアプリをインストールし、Test Analyticsを追加したいリポジトリを認証します。
ステップ2:VitestをJUnit形式でテストレポートを出力するように設定
Test AnalyticsはJUnit形式のテスト結果を解析することで動作します。
私のプロジェクトでは Vitest を使用しており、幸いにもJUnit形式でテストレポートを出力できます。
そこで、vitest.config.ts に以下の設定を追加し、JUnitをテストレポーターとして指定します。
さらに、デフォルトのレポーターも追加することで、普段どおりターミナルでテスト結果を確認できるようにしています。
ちなみに、Vitestはブラウザモードでも異なるレポーターやコードカバレッジをサポートしています。
ステップ3:テストレポートをCodecovにアップロード
最後に、CI上で自動テストを実行し、生成されたテストレポートをCodecovにアップロードするだけです。
ここでは GitHub Actions を使用します。
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
CODECOV_TOKENを作成する方法については、こちらで確認できます。
また、アップロードアクションは必ず実行されるように設定します(${{ !cancelled() }})。
これにより、失敗したテストの実行結果も分析可能になります。
Codecovでテスト分析結果を確認する
セットアップが完了すると、プルリクエスト上でテスト結果の概要や、検出されたフレーク性のレポートを確認できるようになります。
もし私が追加したテストによるフレーク性を、チームメンバーが見つけた場合、プルリクエストの下にCodecovのレポートが即座に表示されます。
さらに詳細なレポートはCodecovで確認できます。
Codecovにログインした後、リポジトリを開き、「Tests」タブに移動することで、Test Analyticsダッシュボードにアクセスできます。
以下は、mainブランチのテスト分析レポートです。
私はすぐに、私が導入したテストが「renders the list of posts」という部分でフレークしているのを確認できます。これは重大な問題です。
ここには他にも多くのメトリクスがありますが、私にとって最も有用な基準は以下の通りです。
- フレークテスト:信頼できないテストのリスト
- 最も時間のかかるテスト:実行に最も時間がかかるテストのリスト
- 平均フレーク率:信頼できないテストの比率
問題のあるテストを追跡する方法が分かったので、いよいよそのテストを修正する時が来ました。
Codecovから得た洞察を活用してフレークテストを修正
通常、フレークテストはテストスイートから完全に削除するところですが、今回テストを開いてみると、すぐにその根本原因がわかりました。
テスト内でsleepを使うのは非常に良くない考えです。
ほとんどの場合、特定の時間が経過するのを待っているのではなく、特定の状態が変化するのを待っているからです。
根本的な原因がわかったので、すぐに修正案を提案します。
<App />のテストケースでは、私が待っている状態は、ユーザーが投稿に返信し、その投稿リストがUIにレンダリングされることです。そのため、テストをその状況に合わせて書き直す必要があります。
getAllByRoleのクエリをfindAllByRoleに置き換えることで、リスト項目がページに表示されると解決されるPromiseを導入しました。最後に、そのPromiseをawaitして、データの取得にかかる時間にも対応できるようにしました。
これでテストの不安定(フレーク性)は解消されました!🎉
リソース
Test Analyticsのドキュメント
Codecovでフレークテストを検出する方法
フレークテストについてSMARTに考える
GitHubでVitestを使ったTest Analyticsのフル例
IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。