暇 ひまネタNEWS(^o^) | スキマ時間にちょっとしたネタを提供スキマ時間に、話題のまとめを一気読み 全サイト自動巡回中
HIMA NETA ORIGINAL READER
本文だけモード Zenn IT・ガジェット 約19分で読めます

Claude Codeで「QAチーム」を組んだら、効いたのは「捨てる仕組み」だった ― AIだけが見つけた不具合・見逃しは?

読みやすさ この端末に設定を保存します
書体
文字
背景

広告・ランキング・関連記事を除き、配信元の安全な文字装飾だけを残しています。

SREホールディングス株式会社でプロジェクトマネージャ(PM)をしている鍋谷です。

「AIにテストを手伝わせたい。でも、AIの『できました』はどこまで信用していいのか分からない」
そんなモヤモヤ、あなたのチームにもありませんか?

本記事では、Claude Code で「AIのQAチーム」を組み、社内の業務SaaSで1か月、手動の機能テストを回してみた結果として、AIだけが見つけた不具合、AIが見逃した不具合、そしてAIが自信満々に出してきた誤指摘をどう捨てたかを紹介します。

結論

  • Claude Code のスキルとサブエージェントで「リーダー1・テスター複数・懐疑的な検証者」のQAチームを組み、手動の機能テスト(テストケースの実行+探索的テスト)を回しました。
  • AIのテスターが出した指摘は68件。そのうち3割は、もっともらしい誤指摘でした。
  • 効いたのは、誤った指摘をきちんと捨てる仕組みです。68件を起票9件に絞り込み、そのうち8件は、AIのチームだけが見つけた不具合でした。
  • 一方で、AIのテスト範囲内で見逃した不具合が4件ありました。捨てる仕組みでは見逃しは減らないので、PASSを値そのもので確かめる仕組みが別に要ります。

なぜAIでQAチームを組むのか

PMとして、QAの体制づくりが課題でした。QAチームは、開発プロセスの中にずっと出てくるわけではありません。出番は、工程の切れ目のチェックやリリース前の検査が中心です。そのために人のQAチームを常に体制に置いておくのは非効率です。

AIエージェントでチームを組めば、必要なときに必要な規模の体制をその場で組み、テストを実行できます。オンデマンドでQAの体制を組めることに、AIでQAチームを組む意味があると考えています。今回は、QAチームのリーダー以下をAIエージェントで組み、PMの私が指示を出しています。

どんなチームか

私(人間)がやることは、次の4つです。自分で画面は触りません。

  • 仕様を渡す
  • テスト用の各 Chrome にログインする
  • リーダーが作った計画をレビューし、ケースや観点を足す
  • 起票するかを判断する

Claude Code がやることは、それ以外のすべてです。

  • リーダー(メイン会話の Claude):仕様を読み、テスト仕様書と探索的テストの狙い(チャーター)を作って分担する。テスターの指摘を集約し、検証者に回す
  • テスター(サブエージェント):担当のケースを実行し、探索的テストで見つけた指摘を返す
  • 検証者(サブエージェント):指摘を1件ずつ、テスターとは別の方法で観測し直して判定する

これらの定義と、ブラウザを操作する共通部品、ルールを書いた CLAUDE.md をまとめて「キット」と呼んでいます。

テスターごとに Chrome を1つずつ、画面が見える状態で起動し、テスターがそれを操作します。合否は、テスターや検証者が自分で撮ったスクショを開いて決めます。

可視Chromeを4面並べた様子
デモ用のアプリで Chrome を4つ並べたところ。1枚目のタブ(「9222 テスター1」など)がポート番号のラベルです。撮影のため検証者にも1面を割り当てていますが、実運用では検証者はテスターが使い終えた Chrome を再利用します。9223 の下端の赤い通知が、後で出てくる「2秒で消えるトースト」と同じ種類のものです。

対象は社内の業務SaaS 2つで、キットを作りながら、8月20日から9月16日までに6ラウンド(3つの機能の初回テスト3回と、リリース後・修正後・再展開後の確認3回)、合計146件のケースを回しました。小さな規模での結果として読んでください。

AIは自信満々に間違える

実際のラウンドで、AIは何度も自信満々に間違えました。代表的な3つを紹介します。

事例1:「ボタンを押しても無反応」→ 押していなかった

テスターが「生成ボタンが無反応」と重要度「高」で報告してきました。検証者が再現を試みると、テスターのクリックがボタンに当たっていませんでした。画面には同じ名前のボタンが2つあり、クリック用の共通部品(これも Claude Code に書かせたもの)が閉じたパネルに隠れた方を掴んで、画面の左上(0,0)を押して「押せた」と返していたのです。

いまは、見えている要素がなければエラーで止まるようにしています。振り返ると、Playwright の locator.click() を使っていれば防げた可能性が高いと思います。

事例2:「エラーが出ない」→ 2秒だけ出ていた

「保存に失敗してもエラーが出ない」という指摘が、検証も通過しました。起票前に私が見直していて気づいたのですが、エラーは画面の下に2秒だけ出るトーストでした。テスターも検証者も同じ方法でエラーを探していたので、同じ方法で同じ見落としをしていたのです。

それ以来、検証者には「もう一度同じことをする」のではなく、「別の方法で観測する」ことを求めています。エラーはその文言そのもので探し、操作の直後から続けて撮って確かめます。

事例3:「残り回数が一度に2減った」→ 隣のテスターが使っていた

回数制限のある機能で「1回の操作で2回分減る」と報告されました。実際は、別のテスターが同じ組織の別アカウントで同じ機能を使っていて、その分が組織で共有のカウンタに加算されていただけでした。製品は仕様どおりで、観測を汚していたのは、テスターを並列に動かしたこちらの設計です。

ほかにも、マウスが乗っていただけの行を「グレーアウトしている」と報告する、自分のスクショと逆の結論を書く、リーダー自身が偽の再現を3回作る、といったことがありました。

効いたのは「捨てる仕組み」

AIのテスターは、もっともらしい指摘を遠慮なく出してきます。こうした無邪気な指摘をそのまま起票すると、開発者の工数を奪ってしまいます。AIのチームそのものも信用されなくなります。だから「捨てる仕組み」が必要です。指摘が起票に届くまでに4つのステップを置き、それぞれで捨てるべきものを捨てています。

下の図は、今回の6ラウンドで出た指摘が、各ステップでどう捨てられ、起票まで絞り込まれていったかを示したものです。件数は6ラウンドの合計です。

① テスターが指摘を出す(68件)

テスターは、担当のケースを実行して PASS / FAIL を付け、探索的テストで気になったものを指摘として返します。指摘には、スクショと再現手順に加えて「誤操作の可能性をどう潰したか」を自分で書かせます。迷ったら「不具合」と断定せず、「質問」か「気になった点」として出させます。不具合かどうかは仕様と照らして決まり、テスターが画面だけで決められるものではないからです。

② リーダーが集約する(22件を除外 → 46件)

リーダーは、テスターたちの指摘を1つの表にまとめ、テスター同士の重複、仕様への質問、修正が検証環境にまだ入っていない既知の不具合などを除きます。ここでは、リーダー自身の間違いも見つかります。リーダーが書いたテスト仕様書の期待結果が誤っていて、テスター3人がそろって正しい挙動を不具合と報告してきたこともありました。

③ 検証者が別の方法で検証する(15件を却下)

いちばん効いたステップです。検証者は指摘を1件ずつ受け取り、「この指摘は間違っている」という前提で崩しにかかります。事例1の(0,0)クリックや、マウスが乗っていただけの「グレーアウト」は、ここで却下しました。リーダーがデザインの原本を縮小したスクショで読み違えた期待値も、ここで見つかっています。検証者には、次の3つを守らせています。

  • 別の方法で観測する:テスターと同じ方法で確かめると、同じ見落としをする(事例2)
  • 「N回中M回」で書く:5割から8割の確率で起きる不具合を、1回だけ試したテスターが「再現しない」と結論したことがある
  • 潰せなかった対抗仮説を空欄にしない:書かせないと、判定できていない論点が判定済みの顔をして通ってしまう

検証の成果は、却下の数だけではありません。却下が0件だったラウンドでも、4件で症状の書き方が直りました。たとえば「顧客名を空にすると起きる」は、実際には「必須項目のどれかが空だと起きる」で、直す範囲が別物でした。

④ 最後に人が判断する(22件を起票せず → 起票9件)

最後に、検証を通った指摘と保留の3件を私が1件ずつ見て、起票するかを決めます。検証者を通ったから信用できる、とはなりません。 2秒で消えるトーストの件(事例2)や、リーダーが自分のクリックの空振りを「再現した」と思い込んだ件は、検証を通った後にここで止めました。こうして止めた誤指摘は5件あります。③で却下した15件と合わせると、誤指摘は20件で、指摘全体の3割でした。

ほかに、既存のチケットへの追記で済むものや、指摘としてはもっともでも仕様として許容した経緯があるものも、ここで落としました。

このステップは、誰が見るかで捨てる判断が変わります。開発の経緯を知っている人が見れば、それだけ精度が上がります。今回は私がこのシステムの開発にPMとして携わってきたので、仕様として許容した経緯まで判断できました。経緯を知らない第三者のQAが見る場合でも、最後に人が見るステップは欠かせません。AIの検証者を通った指摘にも、誤指摘は残るからです。

見つけたもの、見逃したもの

同じ機能は、開発者や仕様の責任者、外部の手動テスターもテストしていました。その人たちの起票とAIのチームの結果を突き合わせると、AIのチームしか見つけていなかった不具合が8件、AIのテスト範囲内で見逃した不具合が4件でした(判定はまず Claude のサブエージェントに任せ、その結果を私が1件ずつ確かめました)。

AIだけが見つけたもので重かったのは、統計値の集計の誤りです。帳票の中身の整合を確かめるチャーターで見つかり、起票から4日で修正が入りました。入力した検索条件が処理に反映されない不具合も、リリース前に見つかり、翌日に修正されています。

見逃した中で一番重かったのは、帳票の構成(項目が並んでいるか)だけを見てPASSにし、中の値が別の画面で計算した同じ値の14.5倍になっていた件です。ほかに、新しく追加された経路で「金額が0」の境界値を試しておらず、リリース後に見つかった件もありました。

この見逃しで、PASS を確かめる仕組みがなかったことに気づきました。検証者が見るのは上がってきた指摘だけで、PASS は誰も系統立てて見ていませんでした。見逃しが出たラウンドのケースをさかのぼって見直すと、値そのものを確かめていたケースは1件もありませんでした。誤指摘を捨てる仕組みは、偽のPASSには効きません。いまは期待結果に「金額の欄が並んでいること」ではなく「金額が別の画面の値と一致すること」と、値そのものを書くようにしています。

参考資料

起票前の誤指摘チェックリスト

AIテスターに限らず、人のテストでも使えます。

  • 「無反応」:そのクリックは、見えている要素の画面内の座標に当たっていたか
  • 「無反応」:同じ名前の要素が、隠れた場所にもう1つないか
  • 「表示されない」:2〜3秒で消える表示(トースト)を連写で見たか
  • 「表示されない」:ホバー時にしか出ない要素ではないか
  • 「色が違う」:マウスを退避してから撮ったか
  • 「値がおかしい」:並行して動いている他のテスターの影響ではないか(共有カウンタ、共有データ)
  • 「再現する/しない」:N回中M回で書いたか
  • 「機能がない」:URLと画面タイトルで、対象の画面にいることを確かめたか。アカウントの設定やプランの差ではないか
  • 「デグレ」:比較対象の環境でも同じ操作をして、両方のスクショを撮ったか
  • 「PASS」:構成だけでなく、値そのものを別の画面で出した値と突き合わせたか

AIに越えさせない線

プロジェクトの CLAUDE.md に書いているルールです。そのまま貼れる形にしています。

  1. ログインしない。 パスワードや二要素認証は触らせず、「9223 で○○アカウントのログインをお願いします」と人に頼ませる
  2. 起票・コメント送信は承認制。 ドラフトを出す → 人が承認する → 実行する
  3. 削除・権限変更・設定変更も承認制。 テストデータは新規作成のみで、既存や共有のデータは壊さない
  4. APIを直接叩いて検証しない。 合否は画面の目視で決める(console や network は「画面に出ない失敗」を拾う補助で、単独の根拠にしない)
  5. 本番環境は触らない。 テストは staging などの検証環境だけで行う
  6. 資格情報をプロンプト・レポート・スクショに出さない
  7. ツールに「行き先の既定値」を持たせない。 対象のURLやパスが指定されていなければ、推測せずに止める

7番目は実際の失敗から生まれました。行き先を省略すると決まった画面を開く共通スクリプトを、行き先を渡さずに3回呼んだだけで、検証環境の監視アラートが3回鳴りました。しかもスクリプトは「件数0」というもっともらしい結果を返していました。

技術的な付録

キット本体は社内運用のため公開していませんが、移しやすい部分を折りたたみに入れておきます。

.claude/agents/adhoc-verifier.md(骨格)
---
name: adhoc-verifier
description: >-
  QAチームの検証者。テスターとは別視点で、あがってきた指摘が正しいかを1件ずつ敵対的に検証する。
  自分の可視Chromeで画面を操作して再現するか、本当に仕様違反か、既知でないかを独立に確認し、
  CONFIRMED/REJECTED/NEEDS-INFO を判定して返す。
tools: Bash, Read, Write, Glob, Grep
---

あなたはQAチームの検証者です。テスターの指摘を鵜呑みにせず、別視点で敵対的に検証します。

## 入力(リーダーがプロンプトで渡す)
検証対象の指摘(テスターが返したJSON)/仕様の該当箇所の抜粋と対象URL/
使ってよいCDPポート(テスターが使い終わったChromeを1つずつ割り当てる)/既知の不具合一覧

## 大原則
- 検証も画面の実表示で行う。スクショを撮り、Read で開いて目視する。APIで裏取りしない。
- 次を積極的に疑う:
  - 仕様通りではないか(仕様を自分で読み直す)
  - テスターの操作ミスではないか(背面の同名ボタン/値が未確定/別画面を触っていた)
  - 撮り方の問題ではないか(トーストが写らないだけ)
  - 既知の不具合の再掲ではないか
  - テスターの期待表示のほうが誤りではないか
- 「もう一度同じことをする」のではなく「別の方法で観測する」。
  テスターと同じ観測方法を使うと、同じ見落としをする。
- 1回の結果で判定しない。必ず「N回中M回」を書く。
- 「無反応」を再現するときは、クリック前に要素を画面内へスクロールして座標を取り直す。
  自分のクリックも空振りして偽の再現を作ることがある。
- 色・濃さの検証は、マウスを画面隅へ退避してから撮り直す。

## 安全境界
ログインしない・破壊的操作をしない・起票しない・資格情報を出力しない。作ったテストデータは消さない。

## 出力(JSON配列)
- 判定: CONFIRMED | REJECTED | NEEDS-INFO(但し書きを付けない)
- 精密化した症状文(CONFIRMED でも必須)
- 再現手順(省略せず1手順ずつ実値で)
- 試行回数と結果(例: 3回中3回)
- 他機能との突合(この機能に固有か共通か。未確認なら未確認と書く)
- 潰した対抗仮説/潰せていない対抗仮説(空にしない)
- 再現エビデンス(自分が撮ったスクショ)
- 正しい解釈(REJECTED の場合)/不足情報(NEEDS-INFO の場合)

tools に Write を入れているのは、検証者に再現用のスクリプトを書かせるためです。

テスターの出力フォーマット
{
  "テストケース結果": [
    { "ID": "TC-01", "判定": "PASS | FAIL | BLOCKED",
      "実際に見えた表示": "<目視で観測した画面>",
      "エビデンス": ["shots/tester-2/tc01.png"],
      "備考": "<BLOCKEDの理由、FAILの差分、試した表示モード>" }
  ],
  "指摘": [
    { "種別": "不具合 | 質問 | 気になった点",
      "重要度": "高 | 中 | 低",
      "出所": "TC-03 | 探索(CH-2)",
      "タイトル": "簡潔な事象",
      "再現手順": ["手順1) ...", "手順2) ..."],
      "画面上の期待表示": "<仕様の根拠つき>",
      "画面上で実際に見えた表示": "<目視で観測した内容>",
      "エビデンス": ["shots/tester-2/xxx.png"],
      "silent_failure補足": "<画面には出ないが console/network に出た失敗があれば>",
      "自己検証": "<誤操作の可能性をどう潰したか>" }
  ],
  "探索メモ": "<指摘にならなかったが見た観点。次ラウンドの手がかり>"
}
起動と接続の例
Chromeの起動(Windows・テスター1人分)
# Chrome をユーザー単位で入れた場合は "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
& "C:\Program Files\Google\Chrome\Application\chrome.exe" `
  --remote-debugging-port=9222 `
  --user-data-dir="C:\qa\profiles\profile-1" `
  --no-first-run --no-default-browser-check `
  "https://staging.example.com/"

起動したウィンドウで対象のアプリを開き、必要ならログインしておきます(ここは人の作業です)。

connect.js
// npm i playwright-core(1.63.0 で確認)
// CommonJS で書いています("type": "module" のプロジェクトでは connect.cjs という名前で保存する)
const { chromium } = require('playwright-core');

(async () => {
  // 行き先は推測しない。指定が無ければ止める
  const port = process.env.CDP_PORT;
  const target = process.env.TARGET_URL_PREFIX;
  if (!port || !target) throw new Error('CDP_PORT と TARGET_URL_PREFIX を指定してください');

  // 起動済みの可視Chromeに接続する(新しいブラウザは立てない)
  const browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
  const page = browser.contexts()[0].pages().find(p => p.url().startsWith(target));
  if (!page) throw new Error(`${target} を開いているタブがありません(ポート ${port})`);

  await page.screenshot({ path: `shot-${port}.png` });
  console.log('撮影:', page.url());
  // CDP接続を閉じるだけ。connectOverCDP で繋いだ Chrome 本体は閉じない
  await browser.close();
})().catch(e => { console.error(e.message); process.exit(1); });
実行(PowerShell)
$env:CDP_PORT = '9222'
$env:TARGET_URL_PREFIX = 'https://staging.example.com/'
node connect.js
# $env: の値はこのウィンドウを閉じるまで残る。別のポートで試すときは両方とも設定し直す

おわりに

AIエージェントでチームを作ってみて感じたのは、人間のチームを作るのと同じだということです。役割を決め、仕事の進め方を決める。仕事を任せれば失敗もするので、そのたびに原因と対策を伝えて直させる。まるで人を相手にしているようでした。

一方で、決めたことをやり切る実行力はすごいです。欲を言えば、「言われたこと以外にも、これもやっておきました」と、自分から穴を埋めにくる動きもしてほしいところです。もっとも、それも指示の仕方次第なのかもしれません。

大変なところもあります。

  • キットを作り、直し続ける手間:8月19日から9月末まで、ほぼ1人で作りました(作業した日は24日、コミットは140回)。いまも落とし穴を見つけるたびに直しています
  • ログインは人の仕事のまま:Chrome が4つなら4回ログインが必要で、セッションも数日で切れます
  • ツールを直すと、別の嘘が出る:ラジオボタンの「選べた」判定を厳しくしたら、選んだ瞬間に閉じる部品を「選べていない」と誤判定するようになりました
  • トークンはかかる:テスターも検証者もスクショを開いて確かめるので、画像の読み込みが多くなります

大変さはあっても、必要なときに必要な体制を組めるAIのチームには、QAの体制として意味があると感じています。そして、チームを組む価値は、人が画面を触らずにテストを回せることと同じくらい、お互いの観測を疑える仕組みを作れることにあると思っています。

SRE Holdings 株式会社

SRE Holdings株式会社の情報発信用ブログです

Discussion

元のページを確認する

必要な場合のみ、広告などを含む配信元ページをフレームで表示できます。