モード変更


    言語

ゴールデンテスト - AI駆動開発における実践的なテストコードを考える

2026/05/19

モンスターラボのエンジニアリングマネージャーの奥田です。

AIによりコードが大量に生成されるようになった今、ソフトウェアの品質担保はこれまで以上に重要なテーマとなりました。品質担保のためにテストコードを書くというのは多くの開発現場で行われていますし、テストコードも生成AIが書けるようになったことでテストカバレッジは大幅に向上していると思います。

一方で、大量に生成されたプロダクトコード、テストコードをチェックするのは大変な作業ですし、AIなどのツールを利用するにしてもガードレールとチェックポイントを適切に設定する必要があります。

本記事では、ゴールデンテスト(Golden Test) というテスト手法を紹介し、AI時代のソフトウェア開発における実践的な適用方法と、AI駆動開発との相性の良さについて解説します。


ゴールデンテストとは

ゴールデンテストは、テストコードの出力を「ゴールデンファイル」と呼ばれる期待値ファイルと比較するテスト手法です。VRT(Visual Regression Test)と呼ばれる視覚的な正しさを担保する回帰テストなどで使われることが多いと思います。 通常のユニットテストでは期待値をコード内にアサーションとして記述しますが、ゴールデンテストでは期待値を外部ファイルとして管理し、テスト実行時に実際の出力とファイルの中身を比較します。

基本的な流れ

  1. テスト対象の関数/APIを実行し、出力を取得する
  2. ゴールデンファイル(期待値ファイル)が存在しない場合 → 出力をそのまま保存する
  3. ゴールデンファイルが存在する場合 → 出力とファイルの内容を比較する
  4. 差分がある場合 → テスト失敗として差分を報告する
  5. 意図した変更の場合 → ゴールデンファイルを更新してコミットする

通常のユニットテストとの違い

観点ユニットテストゴールデンテスト
期待値の管理場所テストコード内外部ファイル
期待値の作成手動で記述初回実行時に自動生成可能
大きな出力への対応アサーションが膨大になるファイル比較で簡潔に対応
変更検知明示的にチェックした箇所のみ出力全体の変更を検知
期待値の更新テストコードを手動で修正ファイルを更新してコミット

ゴールデンテストの何がいいのか

テストコードをシンプルに保ちやすい

関数やAPIの入力を定義し、出力はゴールデンファイルと比較するだけなのでテストコードをシンプルに保ちやすいです。

リファクタリング時のテスト更新コストが低い

コードをリファクタリングした結果、出力のフォーマットが微妙に変わることがあります。個別のアサーションを大量に持つテストではどこが変わったのかを把握し、失敗するアサーションに適切に対処する必要があります。そもそも、適切なアサーションがなければ出力の微妙な変化に気づけません。ゴールデンテストであれば、ゴールデンファイルの差分により確実に差分を検出できますし、ゴールデンテストの差分に問題がなければコミットするだけでテストを更新できます。

人がレビューしやすい

GitHub上のプルリクエストなどでコード差分をレビューする際、ゴールデンファイルを見るだけで差分があるのかないのか、あるとしたらどこなのかをすぐに確認することができます。ゴールデンファイルが画像であってもテキストであっても、レビューしやすく便利です。

# git diff で出力の変更が一目瞭然
- {"status": "ok", "data": {"userId": 1, "name": "Taro"}}
+ {"status": "ok", "data": {"userId": 1, "name": "Taro", "email": "taro@example.com"}}

「何が変わったか」をアサーションの修正履歴ではなく、出力の差分として直接把握できるため、レビューの効率が劇的に向上します。

AIが意味のないテストケースを生成しにくい

AI が生成するテストコードは、テストを通すための無意味なアサーションや不適切なモックを使いがちです。大量の無意味なテストケースにより本質的に確認すべきポイントが見失われ、テストコード自体のメンテナンスコストが上昇し、テストを書く効率が落ちる一方で品質が向上しないという本末転倒な事態にも陥りかねません。 ゴールデンテストの場合、ゴールデンファイルが出力の期待値であるため、AIが期待値を「捏造」するリスクがありません。

ゴールデンファイルは仕様書にもなる

入力と出力を適切に出力すれば、ゴールデンファイルは仕様書にもなりえます。

私が過去に担当したプロジェクトでは、OpenAPIなどでAPI仕様書を記述せず、ゴールデンファイルにリクエスト、レスポンスを出力することでAPI仕様書として管理していました。

また別のプロジェクトでは、生成AIに投げるシステムプロンプトをユーザ状態に応じて合成して生成していたので、そのシステムプロンプトのテストをゴールデンテストで記述しました。ユーザ状態ごとに生成されたシステムプロンプトをゴールデンファイルで確認できるので便利でした。

実装から出力されるのでコードとの乖離もなく、プロジェクト次第では仕様書として十分機能すると思います。

ゴールデンテストが向いているテスト

API レスポンスの回帰テスト

REST API や GraphQL のリクエスト/レスポンスは、構造が複雑でフィールド数も多くなりがちです。個々のフィールドに対してアサーションを書くよりも、レスポンス全体をゴールデンファイルとして管理する方が効率的です。またゴールデンファイルにリクエスト、レスポンスを出力することでAPIの仕様書として扱うこともできます。

データ変換処理の検証

CSV → JSON、XML → オブジェクトなど、入出力の変換処理は出力が大きく予測しづらいケースがあります。ゴールデンテストなら、変更差分を簡単にチェックできます。

設定ファイル・テンプレートの生成

Infrastructure as Code のテンプレート(CloudFormation、Terraform)や設定ファイルの生成結果を検証する際にも有効です。

適用に注意が必要なケース

  • 非決定的な出力(タイムスタンプ、UUID、ランダム値)を含む場合は、マスキングや正規化が必要です
  • 環境依存の出力(ホスト名、パスなど)がある場合は、テスト環境を固定するか出力をサニタイズする
  • そもそもゴールデンファイルとの差分を検知するため回帰テスト向きです。新たにテストケースを作成した場合はゴールデンファイルに問題がないかしっかり確認する必要があります

導入方法

ここでは、TypeScript(Jest)を使ったバックエンドAPIのゴールデンテスト導入例を示します。

Step 1: ゴールデンテストユーティリティの作成

// src/test-utils/golden.ts
import * as fs from "fs";
import * as path from "path";

const GOLDEN_DIR = path.resolve(__dirname, "../../__golden__");
const UPDATE_GOLDEN = process.env.UPDATE_GOLDEN === "true";

/**
 * 実際の出力をゴールデンファイルと比較する。
 * ゴールデンファイルが存在しない場合、または UPDATE_GOLDEN=true の場合はファイルを更新する。
 */
export function expectMatchesGoldenFile(
  testName: string,
  actual: unknown
): void {
  const sanitized = sanitizeOutput(actual);
  const content = JSON.stringify(sanitized, null, 2) + "\n";
  const filePath = path.join(GOLDEN_DIR, `${testName}.json`);

  if (!fs.existsSync(GOLDEN_DIR)) {
    fs.mkdirSync(GOLDEN_DIR, { recursive: true });
  }

  if (UPDATE_GOLDEN || !fs.existsSync(filePath)) {
    fs.writeFileSync(filePath, content, "utf-8");
    if (!fs.existsSync(filePath)) {
      console.log(`Golden file created: ${filePath}`);
    }
    return;
  }

  const expected = fs.readFileSync(filePath, "utf-8");
  expect(content).toBe(expected);
}

/**
 * 非決定的な値(タイムスタンプ、UUIDなど)をマスキングする
 */
function sanitizeOutput(obj: unknown): unknown {
  if (obj === null || obj === undefined) return obj;
  if (typeof obj === "string") {
    return obj
      .replace(
        /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/g,
        "<<TIMESTAMP>>"
      )
      .replace(
        /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,
        "<<UUID>>"
      );
  }
  if (Array.isArray(obj)) return obj.map(sanitizeOutput);
  if (typeof obj === "object") {
    const result: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
      result[key] = sanitizeOutput(value);
    }
    return result;
  }
  return obj;
}

Step 2: API ハンドラーのゴールデンテストを書く

// src/handlers/__tests__/getUser.golden.test.ts
import { expectMatchesGoldenFile } from "../../test-utils/golden";
import { handler } from "../getUser";

describe("GET /users/:id - Golden Tests", () => {
  it("正常系: ユーザー取得", async () => {
    const event = {
      pathParameters: { id: "user-001" },
      headers: { authorization: "Bearer test-token" },
    };

    const response = await handler(event);

    expectMatchesGoldenFile("getUser/success", {
      statusCode: response.statusCode,
      body: JSON.parse(response.body),
    });
  });

  it("異常系: 存在しないユーザー", async () => {
    const event = {
      pathParameters: { id: "non-existent" },
      headers: { authorization: "Bearer test-token" },
    };

    const response = await handler(event);

    expectMatchesGoldenFile("getUser/not-found", {
      statusCode: response.statusCode,
      body: JSON.parse(response.body),
    });
  });
});

Step 3: npm scripts の設定

{
  "scripts": {
    "test": "jest",
    "test:update-golden": "UPDATE_GOLDEN=true jest"
  }
}

Step 4: ゴールデンファイルの運用

# 初回実行: ゴールデンファイルが自動生成される
npm run test:update-golden

# 生成されたファイルを確認
cat __golden__/getUser/success.json

# 内容に問題がなければコミット
git add __golden__/
git commit -m "Add golden files for getUser API"

# 以降は通常のテストとして実行
npm test

# コード変更後にゴールデンファイルを更新する場合
npm run test:update-golden
git diff __golden__/  # 差分を確認
git add __golden__/ && git commit -m "Update golden files"

生成されるゴールデンファイルの例

// __golden__/getUser/success.json
{
  "statusCode": 200,
  "body": {
    "id": "user-001",
    "name": "山田太郎",
    "email": "taro@example.com",
    "createdAt": "<<TIMESTAMP>>",
    "updatedAt": "<<TIMESTAMP>>"
  }
}
// __golden__/getUser/not-found.json
{
  "statusCode": 404,
  "body": {
    "error": {
      "code": "USER_NOT_FOUND",
      "message": "指定されたユーザーが見つかりません"
    }
  }
}

まとめ

ゴールデンテストはシンプルですが、AI駆動開発が主流となった現代でも活用できるテスト手法だと思います。

AIがコードを書く時代だからこそ、「何が変わったか」を確実に可視化できる仕組みが重要です。ゴールデンテストは、その仕組みを最小限のコストで実現する、実践的な選択肢と言えるでしょう。

test

Author

Shuhei Okuda

Shuhei Okuda

エンジニアリングマネージャー/バックエンド/テックリード

電機メーカーのソフトウェア開発部門、SIerを経て2019年1月にモンスターラボ入社。バックエンドチームのエンジニアリングマネージャー。プロジェクトではテックリードとして主にバックエンド開発やインフラ構築を担当。2児の父。好きな音楽のジャンルはジャズ、ラテン、アフロ、ダブなど。

その他おすすめ記事

2026/05/14

「ビジネスアナリストが仕様を書き、エンジニアが実装する」をAI時代に再設計する ── Spec Kitをオフショア開発にカスタマイズした話(設計編)

本記事では、github/spec-kit(以下、Spec Kit)をオフショア × AI駆動開発のプロジェクトに導入するにあたって、標準のSpec Kitに対してどんな設計判断を重ねてきたかを書き残します。本格運用はこれから始まります。だからこそ、判断のプロセスと、設計時点で見えている懸念を、後から検証可能な形で残しておきたいと考えました。 オフショア × AI駆動開発で感じている摩擦 ある一覧APIの仕様書には、「並び順に従って表示する」とだけ書いてあります。実装したエンジニアは、既存の同様のAPIに...

Daisuke Oba

Daisuke Oba

Architecture

2026/04/30

CSS:@property を書くとカスタムプロパティの継承値が変わる

CSS のコンテナクエリを @property と組み合わせたときに予想外の挙動に出会い、原因を追ううちに「CSS のプロパティ値処理」の仕様にたどり着きました。本記事では、その仕様を踏まえて @property 有無で挙動が変わる理由を解説し、学んだ知識で他の CSS 挙動(line-height や width の % 解釈)も読み解いていきます。 前提: @property・コンテナクエリ・cqi まず状況を説明する上で前提となる、@property・コンテナクエリ・cqi の 3 つについて簡単...

Tatsunori Zenko

Tatsunori Zenko

Frontend

サービス開発実績会社情報
採用情報インサイトお問い合わせ
© 2022 Monstarlab
情報セキュリティ基本方針個人情報の取り扱いについて個人情報保護方針