/note/tech

AIで膨らみ続けるテストケースを、リスクベースドテストで選んで回すまでの記録

要約:

■ 1. 背景と目的

  • 対象:
    • PIVOTのモバイルアプリ(iOS / Android)のUAT(受け入れテスト)
  • 問題の発端:
    • AIでテストケースを書くようになってから、ケースの数が増え続けている
    • リリース前のUATで全件を回すことが難しくなった
  • 方針:
    • リスクベースドテスト(RBT)で回すケースを選ぶ
    • ルールは一度では決まらず、何度も作り直している

■ 2. テストケースを仕様書として使う運用

  • テストケースの位置づけ:
    • 機能仕様書を別に作らず、テストケースを今のアプリの挙動の正本にしている
  • 成果物の3分類:
    • テスト観点: GitHubのリポジトリに置く、機能ごとに何を確かめるかの一覧
    • テストケースの定義: GitHubのリポジトリに置く、条件ごとの挙動を1行ずつ書いたもので仕様書を兼ねる
    • 実行記録: Notionに置く、リリースごとに定義から回すケースを写して結果を書く使い捨ての記録
  • 定義の更新方法:
    • 機能を変えるたびに、そのPRで行を追加、書き換え、削除する
    • PRのレビューを通るので、定義を読めば今の挙動が分かる
    • 実行結果は定義には書かず、リリースごとの実行記録に書く
  • ケースが細かくなる理由:
    • 仕様書を兼ねるため、条件の違いを1つずつケースに起こす
    • 例として、非会員、ログアウト後、iPad、戻る操作、別画面からの遷移を個別のケースにする
  • この分け方の利点:
    • RBT導入時も、定義は1行も消さずに済んだ
    • 回すケースを選ぶのは実行記録の上だけで完結する

■ 3. 課題

  • AIによるケースの増加:
    • PRD、Figma、PRの差分からAIでテストケースを書いている
    • 細かいケースでもAIなら短時間で書けるため、機能開発のペースでケースが増える
    • 1回のリリースでUATの実行対象が277件になったこともある
  • 削減基準が残らない問題:
    • 従来は、今回のリリースで動いた機能のケースを全件回していた
    • ケースが細かくなるほど、UATの件数も同じだけ増える
    • 件数を見てケースを削ることもあったが、削った基準は記録に残らなかった
    • 何を回さなかったかは実行記録で分かるが、なぜ回さなくてよいと判断したかは次回参照できなかった
  • 解決の方向性:
    • ケースは仕様書なので減らしたくない
    • ケースはそのまま残し、UATで回すケースだけをリリースごとに説明できる形で選ぶ

■ 4. 機能ベースのRBT

  • 不変の原則:
    • 「影響度 × 発生確率」でリスクを見る原則は最初から変えていない
    • 変えてきたのは、影響度を付ける単位とケースの配分
  • 影響度の付け方:
    • 最初は機能ごとに影響度を付けた
    • 例として、再生やログインは高、フォローやマイルの表示は低とした
  • 発生確率の付け方:
    • その機能の挙動を変えたなら3
    • 内部の置き換えだけなら2
    • 触っていないが波及しうるなら1
  • ケースの種類分け:
    • 何の違いを確かめているかで、正常系・状態・操作・画面・異常の5種類に分けた
    • リスクが高い機能ほど多くの種類を回すようにした
    • UATの不具合は正常系よりも条件の違いから出ることが多かったため
  • 過去5回のリリースでの検証:
    • 回すケース数は候補全件の83%(980件中809件)になった
    • 当時のUATの不具合13件のうち11件を拾えていた
  • 網羅を優先した場合:
    • 条件の違いを細かく拾うように選べば、5回とも落ちは0になる
    • ただし回すケース数は候補全件の92%となり、ほとんど減らない
  • 拾えなかった2件:
    • 条件が限られていて、ユーザーが自分で回避できる不具合だった
  • 判断:
    • 回すケース数は候補全件の80%前後に抑える
    • 残りはリスクとして受け入れる

■ 5. リファクタで露呈した機能ベースの限界

  • iOSアプリのマルチモジュール化:
    • ほぼ全画面のディレクトリ構成が変わった
    • ほぼすべての機能の発生確率が2になった
  • 機能単位の問題:
    • 影響度が高い機能のリスクが上がると、その機能のケースがまとめて回る側に入る
    • 機能Aのテスト観点が1から10まであれば、全部をさらうことになる
    • 結果として候補は1,339件になった
  • 機能内の重さの混在:
    • 1つの機能の中に、満たされないと動画を見られなくなる確認と表示だけの確認が混ざる
    • 影響度を機能単位で付ける限り、この2つを分けられない
  • 当時の対処:
    • 人が5回ほど絞り込んで516件にした

■ 6. ユーザーストーリー × 受け入れ基準による影響度

  • 台帳の作り直し:
    • ケースをユーザーストーリーと受け入れ基準に当て直した
    • 受け入れ基準ごとに影響度を付けた台帳を作った
  • 影響度の定義:
    • 基準が満たされないとき、使おうとした1人に何が起きるかで4段階にした
  • 4段階の内容:
    • S1 見られない: 動画を見る目的を果たせず、代わりの道もない
    • S2 取り返しがつかない: 直した後も元に戻らない、または収益に直結する
    • S3 遠回り・劣化: 目的は果たせるが、手間が増えるか体験が落ちる
    • S4 付加機能が使えない: 中核と関係のない機能が使えない
  • 再生ストーリーでの例:
    • 「再生ボタンを押すと再生が始まる」は、満たされないと動画を見られないのでS1
    • 「再生速度を変えられる」は、満たされなくても通常速度で見られるのでS3
  • テスト観点を単位にしなかった理由:
    • 観点はリリースごとにPR単位で積み上がっている
    • 重さの違う確認が1つの観点に混ざったり、別の観点が同じことを見ていたりした
    • 受け入れ基準なら、1つの基準が1つの振る舞いだけを見るように書ける
  • 発生確率も受け入れ基準ごとに付与:
    • 今回のPRで振る舞いが変わる受け入れ基準は3
    • コードの置き場が移っただけのものや、波及しうるだけのものは2か1

■ 7. 配分の見直し

  • 最初の配分で件数が減らなかった:
    • マルチモジュール化のリリースに当て直すと1,046件で、機能ベースの1,339件からあまり減らなかった
  • 原因:
    • 単位ではなく、発生確率が2か1の受け入れ基準にどれだけ回すかの決め方にあった
    • 最初はS3の基準なら、発生確率が2か1でも正常系を1件ずつ回すことにしていた
    • マルチモジュール化ではほとんどの基準の振る舞いが変わらず、多くがS3かつ発生確率2になった
    • 結果として、ほぼすべての基準で少なくとも1件が回り、回さない基準がほとんど残らなかった
  • 得られた教訓:
    • 単位を細かくしただけでは件数は減らない
    • どの基準を回さないかを決めて、はじめて減る
  • 配分の変更:
    • 発生確率が2か1のS3・S4の基準は、過去に不具合が出た条件のケースだけを回す
    • S3はケースとしては回さないが、アドホックテストで気にかけて見る
  • 現行の選び方(発生確率3の場合):
    • S1、S2、S3は全件
    • S4は正常系1件
  • 現行の選び方(発生確率2・1の場合):
    • S1は全件
    • S2は正常系1件と、過去に不具合が出た条件
    • S3は過去に不具合が出た条件だけで、ほかはアドホックテストで気にかけて見る
    • S4は過去に不具合が出た条件だけ
  • 点数化を採用しなかった理由:
    • 影響度と発生確率を掛けて点数を出す段は置かなかった
    • 表を見れば、なぜそのケースを回すのかがそのまま読める
    • そのほうが、PMと残存リスクを話すときに楽だった

■ 8. Claude Codeのskillによる選定の自動化

  • 自動化の理由:
    • ルールを直すたびに手で選び直すのは大変なため、選定もAIに任せている
  • skillの処理の流れ:
    • リリースチケットを渡すと、今回のPRを集めて機能ごとにまとめる
    • 発生確率を根拠付きで人に確認する
    • スクリプトでケースを選び、Notionに実行記録を作る
  • 位置づけ:
    • AIで増えたケースを、AIで選んで回す形
  • ルールの正本:
    • ルールの正本は文書に置き、skillと食い違ったら文書を正とする
    • 何度も作り直すと分かっていたので、人が読める文書を先に直せるようにしたかった

■ 9. 効果

  • 回すケース数がおよそ半分:
    • マルチモジュール化のリリースで、機能ベースは1,339件、受け入れ基準単位の最初の配分は1,046件
    • 配分を見直したルールで当て直すと628件になった
    • 628件は、S4でも過去に不具合が出た条件は回すという今の扱いを入れる前の数字
  • 回さなかった基準の説明可能性:
    • どの受け入れ基準を、どういう基準で回さなかったかが実行記録に残る
    • 影響度と発生確率の組み合わせが書いてあるため、リリース後の不具合時に判断の妥当性を見直せる
    • 台帳の影響度を直すべきかも見直せる
  • 探索への時間の再配分:
    • 過去を見直すと、ケースに対応しないUATの不具合はもともと探索的テストで見つかっていた
    • 空いた時間は、影響度の高い機能の探索とS3を気にかけて見るアドホックテストに回している

■ 10. まとめ

  • 分かったこと:
    • テストケースを仕様書として扱うとケースは細かくなり、AIで書くとそのペースで数が増える
    • 影響度を機能単位で付けると、リファクタのように広く触るリリースで機能のケースを全部回すことになる
    • 影響度の単位を細かくしただけでは件数は減らず、発生確率が低い基準をどこまで回さないかを決めて減る
    • ケースの選び方だけで、不具合を1件も落とさないことと件数の大幅削減は両立しない
    • どこまでをリスクとして受け入れるかを決める必要がある
  • ケースが膨らんでいるチームへの提言:
    • 影響度は機能ではなく、受け入れ基準のように1つの振る舞いを見る単位で付ける
    • 回すケースを決める表には、影響度と発生確率の組み合わせごとに何件回すかを書く
    • 点数にするより、なぜ回すのかを説明しやすい
    • 新しいルールは、過去のリリースのケースと不具合に当て、件数と拾えた不具合を見てから使う
    • 回さなかったケースは、理由と一緒に実行記録に残す