Google Apps Script でも テスト がしたい! (Clasp + Typescript + Jest)
Google Apps Script(以下,GAS)でライブラリを公開しました。ライブラリを開発する際、テストのフィードバックサイクルを短くするため、Clasp + Typescript + Jest
という技術スタックを選択しました。
その開発体験について共有しようと思います。特段変わったことはしていません。
Google Apps Script のテストってどうしてますか?
script.google.comにアクセスしてデバッグ実行って、しんどくないですか?
- ネットワーク越しでステップ実行するため、遅い
- G Suite 系のサービスと連携すると、サービス側の調整(データ準備とか)が面倒
- デバッグ機能が貧弱
とてもストレスフルです。単純な GAS なら別に良いんですが、少し複雑な GAS を作ろうと思うと、問題に感じます。
ローカルで動かそう
GAS をローカル環境で動かすことができる Clasp というコマンドラインツールが Google より公開されています。
https://github.com/google/clasp
また、Clasp は Typescript をサポートしているため、型を中心としたコーディングが可能となりました。
https://www.npmjs.com/package/@types/google-apps-script
Typescript を選択すると、Interface 設計が容易になります。もちろん、.gs
ファイルでも同様の事は実現できると思います。
次に、Jest と呼ばれるテストツールを組み合わせることで、ローカル環境でテストが可能になります。
https://jestjs.io/docs/getting-started
ただ、単純にテストコードが書けません。 例えば、カレンダーイベントを取得するテストをコーディングするとき、次のようなスクリプトを書いたとします。
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 コンテナを検討してみると良いかもしれません。
https://github.com/inversify/InversifyJS
こうすることで、Jest によるテストが動作するようになります。
実際に、開発・公開したライブラリでも十分にテストをすることができました。
https://www.npmjs.com/package/@silverbirder/caat
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 で予定管理しているチームには是非とも使って頂きたいライブラリです。
https://github.com/silverbirder/caat
CaAT is the Google Apps Script Library that Calculate the Assigned Time in Google Calendar.
このツールでできることは、次のとおりです。
- 指定期間における特定ユーザーの Google Calendar で予定されている時間(分)を取得
- 重複している予定は、連続した予定とみなす
- 指定の時間・単語は、計算対象外とみなす (ランチなど)
- 誰がいつ休みなのか、終日イベントから取得
実際にサンプルコードがあるので、ご参考下さい。
Tags
2020-06-06
みなさん、Zoom使っていますか? ZoomのMeetingを自動生成するGASライブラリを公開しましたので、そのきっかけと使い方について紹介しようと思います。...
2020-02-24
Google Apps Script (以下、GAS)で、困ったことがあったので備忘録として残しておこうと思います。...
2021-11-26
BigQuery、皆さん使っていますか? 私は、業務でBigQueryを使ったデータ構築をしています。品質担保のため、BigQueryのSQLに対してテストをしたいと考えています。本記事では、BigQueryだけで完結し、かつ、Mockデータを差し替え可能なユニットテスト手法について、紹介します。...
2020-11-28
ArchUnitをというものを最近知りました。依存関係のテストができるそうです。さっそく試してみたいと思いますので、その備忘録として残しておきます。...
2020-06-18
最近、Property Based Test という言葉を知りました。他にどういうテストの種類があるのか気になったので、調べてみました。本記事は、テストの種類を列挙します。※ 使用する技術は、私の都合上、node.jsで選んでいます。...
2020-01-12
背景 普段、業務でユニットテストを書いたり、レビューをしたりしています。ユニットテストがあることは良いことなのですが、困ったことがあります。それは、ユニットテストのルールが明確じゃない点です。そのため、人によって、ユニットテストの書き方がマチマチで、なんとかしたいなと困っています。...
2020-11-28
ArchUnitをというものを最近知りました。依存関係のテストができるそうです。さっそく試してみたいと思いますので、その備忘録として残しておきます。...
2019-12-28
Dockerコンテナ上で、 `ts-node-dev --inspect=0.0.0.0:9229 ./dist/index.js` を実行...