ホーム自己紹介ブログ
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 ...
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.com

また、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.com

こうすることで、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.com

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.com
Google
テスト
フロントエンド

-

シェアする

フォローする

次のページ

1コマ漫画検索サービスTiqav2 (Algolia + Cloudinary + Google Cloud Vision API) 作ってみた

前のページ

ユニットテストを書く上で守りたいこと

関連する記事

タグ「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
バックエンド
成果物

タグ「テスト」の記事

CSS Layout Testing というテスト手法の提案

Web のフロントエンド実装において、次のようなミスによってデザイン崩れを起こしてしまったことはありませんか。 flex-shrink の指定を忘れて、要素が押しつぶされてしまった z-index の指定を間違えて、要素が意図せず前面(また

2026-01-10

フロントエンド
テスト
単体テストを全通り書くんじゃない!

AIの進化によって、プロダクションコードに対するテストコードは、以前と比べて格段に書きやすくなったと感じています。 単体テストに関する基本的なお作法については、以前に以下の記事で整理しました。 興味があれば、参考として読んでもらえると嬉しい

2026-01-09

テスト
Playwright の POM を Storybook 上で確認してから E2E テストを書く

はじめに Playwright で E2E テストを書く際、playwright codegen や、近年では Playwright MCP を利用して、テストコードの雛形を作成することが多いと思います。 ただし、生成したテストコードが正し

2025-12-26

テスト

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

ヒューマンインターフェース ガイドライン という言葉を知りました。

最近、ヒューマンインターフェース ガイドライン(HIG)という言葉を知りました。 「ヒューマンインターフェイスガイドライン」には、どのAppleプラットフォームでも優れた体験を設計できるようにするためのガイドとベストプラクティスが含まれてい

2026-01-24

フロントエンド
AIの利用上限に達した時にすることを残しておく

主にWeb関連の個人開発をしている際に心がけていることを書きます。 月末に近づくにつれ、AIの利用上限に達してしまうことがあります。 その状況になった時、以下のいずれかの選択肢が私の中では残っています。 課金して利用上限を増やす 無料モデル

2026-01-22

フロントエンド
AI
CSSで頑張らなくても、SVGで楽にできるときもある

個人サイトをリニューアルをしています。 ノート風のデザインを目指して、スタイルを調整していました。 ノートの見た目は、現実にあるノートを再現しようとCSSを書いていました。 現在、以下の画像のようなノートになっています。 ノート風デザインの

2026-01-20

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