はじめに
こんにちは!QAエンジニアの松永です。
QAの業務は多岐に渡りますが、その中の一つとしてテスト設計があります。
テスト設計という業務の中にはテスト仕様書の作成があり、使用するツールとしてGoogleスプレッドシートを採用しているのですが、テスト設計という作業の中にはどうしても避けられない「地味に面倒な作業」が数多く存在します。
- 面倒な作業の一例
- 準備の面倒: 新しいテストを作成するたびに元となるテスト仕様書をコピーして、命名規則に沿ってリネーム
- 入力の面倒:テスト手順を1つのセル内に箇条書きで書き、各行の先頭に「1. 」「2. 」と手入力し、手順変更のたびにそれらを手で修正するという作業が発生
- 更新の面倒: テストケースの行を1つ追加/削除したら、以降の項目番号全部修正
- 記録の面倒: 誰がいつテストしたのか、担当者と日付を毎回ポチポチ入力
- 集計の面倒: 複数あるテスト仕様書のシートから、全体の進捗を手作業で転記・集計
これらの単純作業を自動化し、もっと本質的なテスト設計や分析に集中したいと考え、Geminiを相棒にGoogleAppsScriptでの自動化に挑戦しました。
GoogleAppsScript(GAS)とは Googleが提供する各種Googleサービスを連携・自動化するためのプログラミング環境。 Gmailやスプレッドシートなどのサービスを連携・自動化ができる。
Google Apps Script: Google Workspace を自動化、統合、拡張。
完成した「自動化機能」の全体像
今回作成したスクリプト
- 【準備の自動化】 ワンクリックでテスト仕様書シートを作成する機能
- 【入力のアシスト】 リアルタイムで手順セル内の自動ナンバリング
- 【集計の自動化】 全シートの状況を瞬時に可視化する機能
各機能のポイントを解説
機能①:準備の自動化
前提:ファイルは以下の画像の構成になっており、テスト設計をする際はマスターシートである[master]を複製して設計をしている

Before:
- 手動で[master]を複製 → 右クリックで名称変更 → 新しい名前を入力の繰り返し
- After:
- スクリプトを実行 → ポップアップにシート名前を全て入力 → 完了 youtu.be
- 補足:[_シナリオテスト仕様書]はデフォルトで命名するように実装
- ポイント:
- エラーを防ぐためにマスターシートの存在確認や、作成済みのシート名との重複チェックも追加。
機能②:手順セル内の自動ナンバリング
- Before:
- 手動で手順番号入力→手順の変更を行う→セル内の手順番号をすべて振り直し
- After:
- 手順の変更を行う→自動的に完了 youtu.be
- ポイント:
- セル内で手順を改行して入力するだけで、「1.」「2.」といった連番が自動で付与・整理されるため、手入力による番号の修正は不要。
- Before:
機能③:集計の自動化
- Before:
- 全てのテスト仕様書シートの集計表からセル参照で値を取得、シートが増減すると合計・進捗周りの数式を修正。
- 手作業のためミスも多い。
- After:
- スクリプトを実行するだけで、全シートへのリンクと最新の集計結果が一覧になったシートが完成。 youtu.be
- ポイント:
- 条件に合致するシートから、必要な情報だけを抜き出して一覧化する集計シートを全自動で作成。
- 自動集計の仕組みは、GASが書き出したシート名の一覧を元に、
INDIRECT関数で各シートの値を自動で参照しているため、常に最新の状態が保てる。
- Before:
Webサイトで見かけたアイデアを、Geminiと「自分のもの」にした方法
- Step 1:ヒントの発見 💡
Webサイトで「セル内の改行テキストに連番を振る」GASのアイデアを見つけ、設計が楽になると直感しました。しかし、コードをそのまま流用はできなかったため、Geminiに聞きながら1から作り直すことにしました。
- Step 2:Geminiとのペアプロ開始 💬
まず、やりたいことの核を、平易な日本語でGeminiに伝えます。
自分:「GASで、特定のセルに書かれた文字列に改行があれば、各行の先頭に『1.』『2.』といった連番を付けたい」
- Step 3:対話によるコードの進化 🤖✨
Geminiが提示した基本的なコードを元に、理想の形になるまで対話を繰り返します。
自分:「ありがとう!でも、すでにある『1.』みたいな番号は、一旦全部消してから振り直したいな」 Gemini:(正規表現 replace を使って、既存の番号をリセットするコードを提案)
自分:「じゃあ、この処理を『手順』という見出しの列だけで動くようにしたいんだけど」 Gemini:(
createTextFinderを使って、ヘッダーの位置を探し、編集された列が一致するか判定するコードを提案)自分:「最後に、セルの内容が変わらない時は、無駄な書き込み処理をスキップして軽くしたい」 Gemini:(処理前後の文字列を比較する
if文を提案)
上記はやり取りの中のほんの一部です。
このようにGeminiは単に答えをくれるだけでなく、対話を通じて思考を整理しコードを改善してくれる最高の壁打ち相手になってくれました。
得られた成果
- 単純作業から解放され、精神的な負担が劇的に減少。
- 手作業によるミスがなくなり、テスト仕様書の品質が向上。
- テスト仕様書のメンテナンスコストが下がったことで、より気軽に内容の改善に取り組めるようになった。
最後に
今回、GASとGeminiを使って、日々の面倒な業務を自動化してみました。
具体的な削減時間の計測はしていませんが、それ以上に「連番ズレを気にするストレス」や「単純作業の煩わしさ」から解放された精神的なメリットが非常に大きかったです。
スプレッドシートが賢くなっていくことで、テスト仕様書をメンテナンスするコストが下がり、もっと気軽に内容の改善に取り組めるようになりました。まさに「テスト仕様書を育てる」という感覚です。
この記事が、同じように日々の面倒な作業に悩む方の、はじめの一歩に繋がれば嬉しいです。
【参考】今回Geminiと作成したコード
機能②:手順セル内の自動ナンバリングのコード
/** * シート編集時に自動実行される関数。 * 編集されたのが複数セルの場合は処理を中断し、手順列の自動採番処理を呼び出す。 * @param {Object} e - Google Sheetsから渡されるイベントオブジェクト */ function onEdit(e) { // 複数のセルが同時に編集された場合は処理を中断 if (e.range.getNumRows() > 1 || e.range.getNumColumns() > 1) { return; } // ヘルパー1:手順列の自動採番処理を呼び出す // 必要な情報をオブジェクトとして渡す const eventData = { range: e.range, editedRow: e.range.getRow(), e: e }; _handleProcedureNumbering(eventData); } /** * ヘルパー1:「手順」列のセルが編集されたら、そのセル内の各行に連番を自動で振る * @param {Object} eventData - onEditから渡されるイベント情報オブジェクト */ function _handleProcedureNumbering({ range, editedRow, e }) { const sheet = e.source.getActiveSheet(); // 1. まず、編集されたセルの上にあるはずのヘッダーを探す(基準:'手順') const headerInfo = _findHeaderInfo(sheet, editedRow - 1, '手順'); if (!headerInfo) return; // ヘッダーが見つからなければ終了 // 2. ヘッダー情報を使って、「手順」列が編集されたかを正確に判断 const procedureCol = headerInfo.headerMap.get('手順'); if (range.getColumn() !== procedureCol || editedRow <= headerInfo.headerRow) return; // 3. セルの値を取得し、中身がなければ何もしない const cellText = range.getValue().toString(); if (cellText.trim() === '') return; // 4. 既存の行番号を消去し、改行で区切って配列にする const lines = cellText.replace(/^\d+\.\s*/gm, '').split('\n').map(line => line.trim()).filter(Boolean); if (lines.length === 0) { range.clearContent(); return; } // 5. 新しい連番を付けてテキストを再生成し、変更があった場合のみセルに書き込む const newText = lines.map((line, index) => `${index + 1}. ${line}`).join('\n'); if (newText !== cellText) { range.setValue(newText); } } /** * 編集された行から上方向に遡ってヘッダー行を探し、その情報をオブジェクトとして返す。 * @param {GoogleAppsScript.Spreadsheet.Sheet} sheet - 対象シート * @param {number} startRow - 検索を開始する行番号(通常は編集された行) * @param {string} keyHeader - ヘッダー行を特定するための必須の列見出し(例: '手順') * @return {object|null} - 見つかった場合: {headerRow: number, headers: string[], headerMap: Map} or null */ function _findHeaderInfo(sheet, startRow, keyHeader) { // 編集行までのデータのみを一度に読み込むことで、処理を高速化 if (startRow < 1) return null; const data = sheet.getRange(1, 1, startRow, sheet.getLastColumn()).getValues(); // 編集行から1行目に向かってループ for (let i = startRow - 1; i >= 0; i--) { const rowData = data[i]; // 行データに必須ヘッダーが含まれているかチェック if (rowData.includes(keyHeader)) { // 見出し名をキー、列番号を値とするMapオブジェクトを作成(高速な検索のため) const headerMap = new Map(); rowData.forEach((header, index) => { if (header) headerMap.set(header, index + 1); }); // 必要な情報をまとめてオブジェクトで返す return { headerRow: i + 1, // ヘッダーの行番号 (1から始まる) headers: rowData, // ヘッダー文字列の配列 headerMap: headerMap // 見出し名と列番号の対応表 }; } } return null; // 最後まで見つからなければnullを返す }