QR Send

スマホとPCのChrome間で、URLやテキストをQRコード1回のペアリングで送り合える自作Chrome拡張。アカウント不要・無料。

使ってみる →

AIに書かせたテストをリポジトリのどこに置くか:tests-ai/で分けてCIを止めない運用にする

Selenium・自動化

レビューは通った。で、このテストどこに置くの

前回、AIが生成したPlaywrightテストをそのまま信じていいかという記事を書きました。弱いアサーション、幻覚セレクタ、ハードコード待機。この3つを潰せばレビューは通ります。ですが実際に日常的に回すようになって困ったのは、その先でした。

レビューを通したテストを既存のtests/にそのまま放り込んだところ、数日後にCIが真っ赤になりました。原因を追うと、数本が環境依存でたまに落ちるだけで、アプリ本体には何の問題もありません。ですが手書きのテストと混ざっているので切り分けに時間を取られ、結局その日は「AIが書いたテストが赤くなったのでCIごと止める」という、一番やってはいけない対応をしてしまいました。

この記事は、レビューを通した後の話です。AI生成テストをどこに置いて、どう回せばCIを止めずに済むか。私が今実際にやっている運用を、設定ファイルごと共有します。特別な機能を使っているわけではなく、Playwright標準のprojectsとretries、それに地味な運用ルールを組み合わせているだけです。だからこそ、明日から真似できると思います。

一発精度は100%ではない、という前提に立つ

ある比較実験(ai.appmatch.jp、複数のAIツールに同一シナリオでテストを生成させた検証)では、生成テストの一発成功率はツールによって77〜85%程度だったと報告されています。私の体感ともだいたい一致します。レビューでロジックとしては正しいと確認できたテストでも、10本に1〜2本は環境やタイミングの揺れで不安定になる余地が最初から含まれているということです。

ここを「そのうち安定するだろう」で放置すると、冒頭のようにCIごと止める羽目になります。最初から一定割合が落ちる前提で、置き場所と回し方を設計してしまおうというのがこの記事の主張です(この数値自体はツールやプロンプトの与え方で変わるはずなので、絶対値として信じるより「一定割合は落ちる」という傾向として受け取っています)。

①ディレクトリを分けて、AI生成分だけ狙い撃ちできるようにする

まず手を付けたのはディレクトリ分離です。手書きのテストとAI生成テストを同じtests/に混在させず、tests-ai/を切りました。AI生成テストの運用を扱った複数の記事(vamp.jpなど)でも共通して勧められているやり方です。

// playwright.config.ts
export default defineConfig({
  projects: [
    { name: 'core', testDir: './tests' },
    { name: 'ai-generated', testDir: './tests-ai', retries: 2 },
  ],
  reporter: [['html', { open: 'never' }]],
  use: { trace: 'retain-on-failure' },
});

projectsでディレクトリごとに設定を分けられるので、AI生成テストだけretriesを厚めにできます。実行時は--projectで絞り込めます(npx playwright test --project=ai-generated)。CIが落ちた時に「どっちが赤いか」が実行結果を見た瞬間にわかるようになり、手書きのcoreが落ちていたらアプリ側の問題として即対応、ai-generatedだけなら次に書く運用ルールに従う、という判断が一発でできます。これ、意外と使えます。

ディレクトリを分けるほどでもない小さいリポジトリなら、ファイル名に.ai.spec.tsのような接尾辞を付けてtestMatchで拾い分ける方法でも同じことができます(まだ試していませんが、testMatchが正規表現でファイル名を指定できる以上、原理的には動くはずです)。私の現場はtests-ai/で十分だったので、無理に複雑にはしていません。

②retriesでフレークのノイズを吸収し、原因はHTMLレポートとtraceだけで見る

ディレクトリを分けても、環境依存でたまに落ちるテストは残ります。毎回原因調査に時間をかけていては本末転倒なので、retriesで一度ノイズを吸収します。Playwright公式のretriesは、失敗したテストを指定回数まで自動で再実行する機能です。私はai-generatedだけretries: 2を設定し、手書きのcoreには基本的に入れません。手書きテストが不安定なら、それはテスト設計を直すべき問題であって、retriesでごまかす話ではないと思っているからです。

それでも安定しないテストは、npx playwright show-reportshow-traceで残したtraceだけを見て原因を判断します。HTMLレポートにはretryごとの結果が残るので、「1回目は失敗したが2回目は通った」のか「毎回同じ箇所で落ちている」のかがひと目でわかります。前者はただのフレークですが、後者は環境依存を装った実バグの可能性があるので手動で深掘りします。何度実行しても同じ箇所で落ちるなら、テストかアプリのどちらかを直すべきです。retriesは調査の手間を減らす道具であって、原因を見なくていい理由にはなりません。

③安定しないものは消さずに一時skipして、再生成キューに回す

retriesを掛けても安定しないテストが出てきたら、私は削除ではなく一時skipを選んでいます。せっかくレビューを通した検証観点を失いたくないからです。

test.skip('クーポン適用後の合計金額が正しく表示される', async ({ page }) => {
  // ...
});
// TODO(regen-queue): CI3回中2回タイムアウト。data-testid化して再生成予定。

skipするだけだと存在を忘れてしまうので、コメントに理由と次のアクションを書いた上で、社内で「再生成キュー」と呼んでいる棚卸し用のMarkdownメモにファイル名とテスト名を書き足しています。中身はこの程度の簡単な表です。

| ファイル | テスト名 | 原因(推測) | 次のアクション |
|---|---|---|---|
| checkout.spec.ts | クーポン適用後の合計金額 | タイムアウト2/3 | data-testid化して再生成 |
| search.spec.ts | 絞り込み結果0件の表示 | 原因未特定 | 手動で再現手順を確認 |

週に一度このメモを見ながら、直すか作り直すか諦めるかを判断するだけの地味な運用です。大事なのはskipしたテストが放置されないことで、サボると最初は3本だったskipがいつの間にか20本になっていた、ということが普通に起こります。skipは対症療法にすぎないので、原因が分かった時点でメモから消してテストを復活させることを忘れないようにしています。

Healerに直させるのとは何が違うのか

以前壊れたテストをHealerに直させる話を書きましたが、今回のskipは考え方が少し違います。Healerはセレクタが変わったなどの「マークアップの変化」にAIが追従してくれる仕組みです。一方、私が一時skipしているのは、原因が環境依存やタイミングの揺れで、そもそも何が起きているかまだ切り分けられていないテストです。原因がはっきりしていないものをAIに自動で直させるのは怖いので、まずは人が棚卸しリストに乗せて判断する、という順番にしています。原因が特定できてからHealerや再生成に頼るのは全く問題ないと思っています。

この3点セットでCIを止めなくなった

ディレクトリ分離・retries・一時skipを組み合わせてから、AI生成テストが原因でCI全体を止めることはなくなりました。手書きテストの信頼性はそのままに、AI生成テストは「多少揺れることはあるが、本当に壊れた時だけ人が見る」という扱いに落ち着いています。

1年目のメンバーがAIにテストを書かせ始めると、最初にぶつかるのが「AIが書いたテストが赤くなったのでCIごと止めた」というパターンです。技術的に難しい問題ではなく、置き場所と回し方を最初に決めていないだけのことがほとんどです。レビューの落とし穴を潰した後は、ぜひ置き場所まで設計してみてください。

関連記事も合わせてどうぞ。

コメント

タイトルとURLをコピーしました