GASに自動テストを導入 ― 業務ロジックの分離とユニットテストの実践

投稿更新日: 2026/9/23

サムネイル

1. はじめに

GASを修正するたびにスプレッドシートへ値を入力し、実行結果を目で確認していませんか?

条件分岐が増えると手作業だけでは確認漏れが起きやすくなります。 規模が小さいうちは問題ありませんが、機能追加に伴ってコードの規模が大きくなってくると確認漏れも増え不具合の原因につながる場合があります。 こうした確認を繰り返し実行できるようにするのが自動テストです。

GASの業務ロジックを、Googleのサービスに依存しないJavaScriptの関数として切り出すことで、さまざまなテストツールを活用することができます。

などが選択肢です。

今回は、その中からJest(ジェスト)を使った自動テストの導入方法を紹介します。

この記事で扱うのは、関数などの小さな単位を確認する「ユニットテスト」です。手元のPCで実行し、次のような確認を自動化します。

  • 金額やステータスが、ルールどおりに判定されるか
  • 空欄や判定の境目となる値でも、期待どおりに動くか
  • 修正した不具合が再発していないか

今回使うコードと事前準備

今回のサンプルコードは、次のGitHubリポジトリで紹介しています。

サンプルコード:gas-jest-example

題材は、申請金額が「50,000円以下なら自動承認、超えたら要確認」と判定する処理です。記事では、主に次の3ファイルを使って説明します。

gas-jest-example/
├── src/
│   └── approval.js             # 承認判定と更新処理
├── tests/
│   ├── approval.test.js        # 判定ルールのテスト
│   └── updateApproval.test.js  # 更新処理のテスト
└── package.json

ここからは、構成を理解するために、手元で最小構成を作る手順を示します。 Node.jsとnpmが利用できる環境で実行してください。

mkdir gas-jest-example
cd gas-jest-example
npm init -y
npm install --save-dev jest

package.jsonの"scripts"にある"test"を変更します。他の項目はそのまま残してください。

"scripts": {
  "test": "jest"
}

今回はCommonJS形式でファイルを読み込むため、"type": "module"は追加しません。 これで、npm testからテストを実行する準備ができました。 参照:Jestの導入手順

2. テストできる構成にする:GAS APIとロジックの分離

ローカルのNode.js環境では、SpreadsheetAppなどのGAS専用APIは使えません。そのため、処理を次のように分けます。

GAS:セルから金額を読む
          ↓
JavaScript:承認区分を判定する ← Jestでテスト
          ↓
GAS:結果をセルに書く

src/approval.jsに、判定する関数を書きます。

function determineApproval(amount) {
  if (!Number.isInteger(amount) || amount < 0) {
    throw new Error('金額は0以上の整数で指定してください');
  }

  return amount <= 50000 ? '自動承認' : '要確認';
}

// Node.jsで読み込んだときだけ公開する
if (typeof module !== 'undefined') {
  module.exports = { determineApproval };
}

この関数は、金額を受け取り、結果を返すだけです。セルの読み書きなど、外部の状態を変更しないので、スプレッドシートなしでテストできます。

今回は、空欄や文字列の"50000"を数値に変換せず、エラーにするルールにしています。

末尾のmodule.exportsは、Node.jsで別ファイルから読み込むための処理です。GASにはこの仕組みがないため、存在確認を付けています。 参考:GASの実行環境と制約

3. 最初のテストを書く

tests/approval.test.jsに、最初のテストを書きます。

const { determineApproval } = require('../src/approval');

test('30,000円なら自動承認になる', () => {
  expect(determineApproval(30000)).toBe('自動承認');
});

test()で確認内容を定義し、expect(実際の結果).toBe(期待する値)で比較します。

作業用フォルダーで、次のコマンドを実行してください。

npm test

成功するとPASSと表示されます。修正後も同じコマンドを実行するだけで、繰り返し確認できます。

4. GAS依存の処理は、引数で差し替える

「金額を読んで、判定結果を書き込む」という流れも、読み書きの関数を外から渡すとテストできます。

src/approval.jsの公開処理の前に、次の関数を追加します。

function updateApproval({ readAmount, writeStatus }) {
  const status = determineApproval(readAmount());
  writeStatus(status);
  return status;
}

末尾の公開処理も置き換えます。

if (typeof module !== 'undefined') {
  module.exports = { determineApproval, updateApproval };
}

GASでは、実際にセルを操作する

GASでは、セルを読み書きする関数を渡します。

function runApproval() {
  const sheet = SpreadsheetApp
    .getActiveSpreadsheet()
    .getSheetByName('申請サンプル');

  if (!sheet) {
    throw new Error('申請サンプルシートが見つかりません');
  }

  updateApproval({
    readAmount: () => sheet.getRange('A2').getValue(),
    writeStatus: (status) => {
      sheet.getRange('B2').setValue(status);
    },
  });
}

試す場合は、スプレッドシートに紐づくGASへapproval.jsの内容と上の関数を配置します。「申請サンプル」シートのA2に金額を入れ、runApproval()を実行すると、B2に結果が書き込まれます。

テストファイルやnode_modulesはGASへ配置しません。

テストでは、代役の関数を渡す

テストでは「モック」と呼ばれる代役の関数を使います。tests/updateApproval.test.jsに書いてみましょう。

const { updateApproval } = require('../src/approval');

test('判定結果を書き込み処理へ渡す', () => {
  const readAmount = jest.fn().mockReturnValue(50001);
  const writeStatus = jest.fn();

  updateApproval({ readAmount, writeStatus });

  expect(writeStatus).toHaveBeenCalledTimes(1);
  expect(writeStatus).toHaveBeenCalledWith('要確認');
});

jest.fn()は呼び出しを記録する関数を作り、mockReturnValue()は返す値を指定します。

この例では、金額として50,001円を返し、書き込み担当の関数に「要確認」が1回渡されたことを確認しています。実際のスプレッドシートへのアクセスは発生しません。 参考:Jestのモック関数

5. 何をテストするか:境界値と回帰テスト

判定が変わる境目を確認する

「50,000円以下」という条件なら、特に確認したいのは境界の前後です。

入力期待する結果
49,999自動承認
50,000自動承認
50,001要確認
空欄・負数・文字列エラー

このような「境界値テスト」は、<=を<と書いてしまうミスなどの検出に役立ちます。

tests/approval.test.jsに追加しましょう。

test.each([
  [0, '自動承認'],
  [49999, '自動承認'],
  [50000, '自動承認'],
  [50001, '要確認'],
])('%i円なら「%s」になる', (amount, expected) => {
  expect(determineApproval(amount)).toBe(expected);
});

test.each([[''], [-1], [1.5], ['50000']])(
  '不正な金額 %p はエラーになる',
  (amount) => {
    expect(() => determineApproval(amount)).toThrow(
      '金額は0以上の整数で指定してください'
    );
  }
);

test.each()を使うと、複数の入力をまとめて確認できます。エラーを検証するときは、expect()へ実行結果ではなく関数を渡します。

修正した不具合をテストに残す

例えば、空欄をNumber()で0へ変換し、自動承認してしまう不具合があったとします。その修正時に「空欄はエラーになる」というテストを残せば、再発に気付けます。

このように、変更によって既存の動作が壊れていないか確認するのが「回帰テスト」です。

ただし、モックを使ったテストでは、実際のシート名・セル指定・アクセス権限までは確認できません。判定パターンはJestで、実際の読み書きは検証用のGAS環境で確認すると役割を分けましょう。

6. まとめ

GASの自動テストは、業務ロジックを独立した関数にすることから始められます。

  • セルの読み書きと、判定ロジックを分ける
  • GAS依存の処理は、引数で差し替える
  • 境界値や過去の不具合を、Jestのテストに残す

今回は、いくつかあるテストツールの中からJestを使いました。処理を分離する考え方は、ほかのツールを選ぶ場合にも活用できます。

まずは、修正するたびに手作業で確認している判定を一つ選んでみてください。その確認をコードに残すだけでも、次の変更に取りかかりやすくなります。

参考

この記事をシェアする

合同会社raisexでは一緒に働く仲間を募集中です。

ご興味のある方は以下の採用情報をご確認ください。