ホーム自己紹介ブログ
NO.39
DATE2020. 02. 01

Google Apps Script でも テスト がしたい! (Clasp + Typescript + Jest)

Google Apps Script(以下,GAS)でライブラリを公開しました。ライブラリを開発する際、 テストのフィードバックサイクルを短くする ため、Clasp + Typescript + Jest という技術スタックを選択しました。 その開発体験について共有しようと思います。特段変わったことはしていません。

Google Apps Script のテストってどうしてますか

script.google.comにアクセスしてデバッグ実行って、しんどくないですか?

Google Apps Script Debugging ...
  • ネットワーク越しでステップ実行するため、 遅い
  • G Suite 系のサービスと連携すると、サービス側の調整(データ準備とか)が 面倒
  • デバッグ機能が 貧弱

とても ストレスフル です。単純な GAS なら別に良いんですが、少し複雑な GAS を作ろうと思うと、問題に感じます。

ローカルで動かそう

GAS をローカル環境で動かすことができる Clasp というコマンドラインツールが Google より公開されています。

GitHub - google/clasp: 🔗 Command Line Apps Script Projects

🔗 Command Line Apps Script Projects. Contribute to google/clasp development by creating an account on GitHub.

GitHub

また、Clasp は Typescript をサポートしているため、型を中心としたコーディングが可能となりました。

npmjs.com

Typescript を選択すると、Interface 設計が容易になります。もちろん、.gs ファイルでも同様の事は実現できると思います。

次に、Jest と呼ばれるテストツールを組み合わせることで、ローカル環境でテストが可能になります。

jestjs.io

ただ、単純にテストコードが書けません。 例えば、カレンダーイベントを取得するテストをコーディングするとき、次のようなスクリプトを書いたとします。

const calendar: Calendar = CalendarApp.getCalendarById(
  "<your google calendar id>"
);
calendar
  .getEvents(new Date("2020-01-01"), new Date("2020-01-02"))
  .forEach((calendarEvent: CalendarEvent) => {
    console.log(calendarEvent.getTitle());
  });

こう書いてしまうと、本当のカレンダーイベントを取りに行ってしまいます。テストであれば、そういった処理は避けたいところです。 そこで、CalendarApp を偽物のオブジェクト、つまり Mock オブジェクトに差し替えるため、依存性逆転の原則(dependency inversion principle)を適用します。

interface ICalendarApp {
  calendars?: Array<ICalendar>;
  getCalendarById(id: string): ICalendar;
}
 
interface ICalendar {
  calendarEvents?: Array<ICalendarEvent>;
  getEvents(startTime: Date, endTime: Date): Array<ICalendarEvent>;
}
 
interface ICalendarEvent {
  title?: string;
  getTitle(): string;
}
 
class CalendarAppMock implements ICalendarApp {
  calendars?: Array<ICalendar>;
 
  getCalendarById(id: string): ICalendar {
    return this.calendars![0].calendar;
  }
}
 
class CalendarAppImpl implements ICalendarApp {
  getCalendarById(id: string): ICalendar {
    const calendar: ICalendar = CalendarApp.getCalendarById(id);
    return calendar;
  }
}

このようなインターフェース・クラスを準備し、先程のコードを次のようにします。

const calendar: ICalendar = new CalendarAppMock().getCalendarById();
calendar
  .getEvents(new Date("2020-01-01"), new Date("2020-01-02"))
  .forEach((calendarEvent: ICalendarEvent) => {
    console.log(calendarEvent.getTitle());
  });

結果、CalendarApp の代わりに Mock オブジェクトを差し込めるようになりました。ローカルテストが可能となります。

もちろん、プロダクトコードでは、CalendarAppMock ではなく、 CalendarAppImpl を使用すれば良いです。 Mock で差し替えるオブジェクトが増えると、InversifyJS のような DI コンテナを検討してみると良いかもしれません。

GitHub - inversify/InversifyJS: Powerful and lightweight inversion of control container. Moved to https://github.com/inversify/monorepo

Powerful and lightweight inversion of control container. Moved to https://github.com/inversify/monorepo - inversify/InversifyJS

GitHub

こうすることで、Jest によるテストが動作するようになります。
実際に、開発・公開したライブラリでも十分にテストをすることができました。

npmjs.com
CaAT $ npm run test -- --coverage
 
> jest "--coverage"
 
 PASS  __tests__/utils/dateUtils.test.ts
 PASS  __tests__/group/groupImpl.test.ts
 PASS  __tests__/member/memberImpl.test.ts
---------------------|---------|----------|---------|---------|-------------------
File                 | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------------|---------|----------|---------|---------|-------------------
All files            |   98.43 |    97.62 |   96.67 |   98.37 |
 __tests__           |     100 |      100 |     100 |     100 |
  generator.ts       |     100 |      100 |     100 |     100 |
 src/calendar        |    93.1 |      100 |   92.31 |   92.59 |
  calendarAppImpl.ts |      60 |      100 |      50 |      60 | 6,7
  calendarAppMock.ts |     100 |      100 |     100 |     100 |
 src/group           |     100 |      100 |     100 |     100 |
  groupImpl.ts       |     100 |      100 |     100 |     100 |
 src/member          |     100 |    94.74 |     100 |     100 |
  memberImpl.ts      |     100 |    94.74 |     100 |     100 | 38
 src/utils           |     100 |      100 |     100 |     100 |
  dateUtils.ts       |     100 |      100 |     100 |     100 |
---------------------|---------|----------|---------|---------|-------------------
 
Test Suites: 3 passed, 3 total
Tests:       23 passed, 23 total
Snapshots:   0 total
Time:        2.826s, estimated 6s
Ran all test suites.

ライブラリとして提供する機能のテストが、たったの 約 3 秒 で終わります。 ストレスフリー にローカル開発が可能となりました。

詳しくは、実際に作ったライブラリのソースコード(__tests__)を御覧ください。

終わりに

GAS は、とても便利です。生産性が向上します。 サクッと API を構築できますし、G Suite との連携も(当たり前ですが)簡単です。

ただ、メンテナンス性が低いコードになると、 陳腐化され誰も面倒が見れなくなります 。 常にクリーンであり続けるためには、テストコードは 必須 です。 GAS を運用する方々には、是非ともテストコードを検討下さい。

え、あ、ちょっとまって。ライブラリの紹介

アジャイル開発で、かつ、Google Calendar で予定管理しているチームには是非とも使って頂きたいライブラリです。

GitHub - silverbirder/CaAT: CaAT is the Google Apps Script Library that Calculate the Assigned Time in Google Calendar.

CaAT is the Google Apps Script Library that Calculate the Assigned Time in Google Calendar. - silverbirder/CaAT

GitHub

CaAT is the Google Apps Script Library that Calculate the Assigned Time in Google Calendar.

このツールでできることは、次のとおりです。

  • 指定期間における特定ユーザーの Google Calendar で予定されている時間(分)を取得
  • 重複している予定は、連続した予定とみなす
  • 指定の時間・単語は、計算対象外とみなす (ランチなど)
  • 誰がいつ休みなのか、終日イベントから取得

実際にサンプルコードがあるので、ご参考下さい。

GitHub - silverbirder/SampleCaAT: This repository is trying the CaAT repotistory

This repository is trying the CaAT repotistory. Contribute to silverbirder/SampleCaAT development by creating an account on GitHub.

GitHub

Google
テスト
フロントエンド

-

読者になる

|

シェアする

|

silverbirders

silverbirder

Webソフトウェアエンジニア
個人ブログキュレーション こぶりー 運営

ブログを応援する

この記事がよかったら、お布施という形で応援してもらえるとうれしいです。

おふせぼたん

※ ログイン不要で投稿できます。

※ 同じブラウザから投稿を削除できます。

0

読み込み中...

次の記事へ前の記事へ

関連する記事

タグ「Google」の記事

1週間で完成!Spotifyタイアップ検索アプリを作った話(駆け出し9年目)

どうも、Web業界で働き始めて9年目の駆け出しエンジニア、silverbirderです。Spotifyで音楽を聴いていると「この曲、どこかで聞いたことがあるけど、何の主題歌だったかな?」と思うこと、ありませんか?特にドライブ中や作業中に、ふと気になることが多いですよね。 私もそんな経験があり、気になったその曲が主題歌だったアニメを見始めたことがきっかけで、「簡単にタイアップ情報(アニメやドラマなど)を調べられるアプリがあったら便利だな」と思い、このアプリを作ることにしました。

2024年09月24日

サービス
AI
Google
成果物
ERNIE-ViLG を Google Colaboratory で動かしてみた

ERNIE-ViLG というのが、"二次元キャラ" に強いという記事を目にしました。実際に使ってみようと、次のページで試したんですが、レスポンスがイマイチでした。そこで、次の記事を参考にして、ERNIE-ViLG を Google Colaboratory で動かすようにしました。

2022年09月03日

AI
Google
クローリング
Googleアカウント画像を返却するだけのAPIを作った

みなさん、ご自身のプロフィール画像ってどう管理していますか?例えば、zennのプロフィール画像って、更新していますか? 私は、プロフィール画像の更新は面倒なので、放置することが多いです。(GravatarみたいなSaaSが使えたら良いのに...)

2021年12月20日

Google
バックエンド
成果物

タグ「テスト」の記事

Testcontainersで開発PR単位にE2Eテストなど

Testcontainers + Playwright + Cucumber.js による受け入れテストを、公私ともに活用しています。 運用していて気づいた点について、また書き残そうと思います。 開発PR単位でE2Eテスト これはTestc

2026年09月07日

テスト
ブローガスト25日目: ミューテーションテストのキルゼロテスト

テストコードをテストする、ミューテーションテスト。 プロダクションコードを突然変異でコードを書き換えたら(ミューテーション)、テストが失敗してくれるかどうか。 失敗するということは、不具合を検知できたということです。 ミューテーションする箇

2026年08月25日

テスト
Testcontainers運用経験から分かった、良い点・困る点

お仕事先のWebアプリケーションに、Testcontainersを導入運用して数ヶ月経過しました。 良い点と困る点について、書き残しておきます。 前提 簡単な前提として、バックエンドDB・APIとフロントエンドの3層構成のよくあるWebアプ

2026年07月08日

テスト

タグ「フロントエンド」の記事

Webmention 使ってみた

Webmention というのを最近知りました。 名前の通り、Web 上でメンションすることができます。 受信側、送信側、どちらも対応したので感想を残そうと思います。 Webmention ざっくりと Web AサイトにBサイトのリンクをつ

2026年09月08日

フロントエンド
個人開発
モジュールアップデートでやっていること

ITヲタクな AI くんにピッタリな仕事の一つは、モジュールアップデート。 特に、メジャーアップデートが超助かる。 昔だったら 数ヶ月掛かった移行も、数日たらずに対応できるようになったので、 さすが ITヲタクくん。 対応してよかったフロー

2026年07月24日

AI
フロントエンド
写経テストコードを全部消した

以下で書いた通り、プロダクトコードを写経したテストコードを削除しました。 "こぶりー" ( https://kobliy.vercel.app/ ) という個人ブログを読むアプリのコードです。 https://silverbirder.gi

2026年06月08日

AI
フロントエンド
テスト
← ブログ一覧へ