Google Apps Script開発をもうちょっとモダンにしてみる
よりモダンな開発サイクルへ
前回から早くも1年半の月日が流れてしまったが、今回のテーマは
- CI/CD と組み合わせてモダンなワークフローの完成形を目指す
- そのために Node.js ベースのテストフレームワークがちゃんと動くようにする
- Google サービスのオブジェクトを stub/mock する
- CoffeeScript をやめて ES2015 へ
の辺り。
前回の記事で残った課題
前回調べた手法を使うと Codegs を利用して Node.js で書きながら 1ソースに build できる。このコードを gas upload すれば GAS 上で動くので、
- コードを手元の好きなエディタで編集し、
- Git でバージョン管理し、
- 適切に module 分割しつつ最終的に GAS で動かす状態に持っていく
ことはできた。ただし、build プロセスが GAS 向けに最適化されており、最終的に全自動にするにはあまり向いていなかった。
gasifyの発見
個人的にはモダン Web 開発の文脈では Browserify と組み合わせてみる実験を
Rails + Browserify + Mithril + cmsxで動くもん書いてみた - あーありがち(2015-09-23)
でしてるんだけど、その後、Google Apps Script 開発方面にこれの影響が来ていたことを知る。
なるほどなるほど。gasify がキモらしい。
fossamagna/gasify: Browserify plugin for Google Apps Script
確かにこれを使うとものすごく手軽に lodash を使うことができた1。これで GAS 用のライブラリになっていない npm の小粒なモジュールも利用可能だ。こりゃーいい。
テスト用ツールチェイン方面の課題
- これまでは Web アプリのテストの自動化をやっていたので Jasmine でやってた
- 最初に日記に書いたのはもう6年半前だよ…
- 今から始めるなら当然 PowerAssert は使いたい
- 以前空っぽのプロジェクトを CoffeeScript で回せるものは作ったが、もう Babel でいいや
ということで
- Mocha - the fun, simple, flexible JavaScript test framework
- babel-register
- power-assert-js/intelli-espower-loader: Handy node module for power-assert and espower-loader.
でいくことにした。
最終的に使ったもの
- browserify
- watchify
- gasify
- babelify
- babel
- babel-preset-es2015
- babel-core
- babel-register
- power-assert
- intelli-espower-loader
- mocha
- sinon
- gas-manager
- lodash
こんな感じ。
CI/CDするうえで課題になる認証情報は環境変数で
今回 GAS の開発フローを自動化しようと思ったきっかけは、手離れさせたいのがいちばん強いんだけど、その次は
Google Developers Japan: Apps Script による高度な開発プロセス
だった。Google が公式に紹介するんだから、これまでの課題はいろいろ解消されているに違いないと思ったのだ。
結論から言うと期待は裏切られた。gas-manager でできることとの違いが自分には分からなかった。以前も書いたが、Google Apps Script を使った開発については日本の GAS コミュニティの方が Google 本家より進んでいるんじゃないかと思う。
さて、gas-manager も node-google-apps-script も何が問題かというと、要は認証情報を JSON ファイルで保存する形になってしまっているので、API キーとか ssh の秘密鍵より取り回ししにくいのだ。
Authentication with Google Cloud Platform - CircleCI
を見つけて「おっ」と思ったが、これは gcloud コマンドを使うことが前提の話で、Google Drive API を使う話ではなかった。が、ここにヒントがあった。
自分でやったことは以下の四つ。今回は GitHub + CircleCI で行ったが、考え方は他のツールを使っても変わらない。
- gas-manager で credential 情報の JSON を作成、保存する
- gas-manager を使って Git + GitHub で開発できる状態に
- gas-project.json は含む
- gas-config.json は .gitignore へ
- npm/yarn run で gas upload / download できるように
- CircleCI の project に環境変数を作る GOOGLE_CREDENTIAL とか。これに上の gas-config.json の内容を放り込む
- circle.yml で以下のように gas-config.json を生成
dependencies:
override:
- echo $GOOGLE_CREDENTIAL > $HOME/$CIRCLE_PROJECT_REPONAME/gas-config.json
- yarn install
これで gas-manager は -c で生成済み gas-config.json を使うことで、認証情報をリポジトリに保存することなく CI 上で認証を通せる。
gasifyを使ってもimport/exportは使えないのでpresetを調整する
確認したバージョンは gasify 0.1.0
最近 JavaScript復習2017 - importとuse strictとメソッド定義 - - あーありがち(2017-02-19)
で勉強したようにイマドキは import / export だよねーと思って喜んで書き始めたのだが、import / export のところで Script Editor がエラーを出す。2
Google Apps Script は独自仕様の JavaScript 構文 + 独自の JavaScript オブジェクトによるサーバサイドスクリプトで、Babel を通したものでも動かないものはあるということです。これは今度は Babel 側の import / export の処理方法に原因があるので、
Google Apps ScriptでES Module(Babel)を使うときのTips - Qiita
gasify の作者が babel preset を作り始めようとしているようで、もしかしかたらこっちの方が本命になるかもしれないけど、まだ何も中身はない模様 ;-)
を設定します。これは ES 3 時代の文法に適合するように transpile してくれるので import / export が正しく動くようになる、というもののようです。
fossamagna/babel-preset-gas: Babel preset for all Goolge Apps Script plugins.
ということで 2017-05時点では module.exports と require() を使いましょう。
Mocha + babel-register + intelli-espower-loaderは順番に注意
確認したバージョンは
- mocha 3.4.1
- intelli-espower-loader 1.0.1
- babel-register 6.24.1
intelli-espower-loader を先に食わせろ。
具体的なコマンドは以下のようになった。
mocha --require intelli-espower-loader --require babel-register --recursive test
Googleサービス用オブジェクトをSinonでstub out
分かったこと。
- gas-local のメリットはよく分からない
- Sinon はちゃんと存在しているメソッドがないと stub out できない
テストのサイクルを速くたくさん回すために CI を利用したい、しかし GAS は Google 上の独自サービスあってこそのもの、Node.js だけではすべての機能を動かすことはできない、だから GAS 独自オブジェクトは stub/mock に差し替えてテストを動かす必要がある。
そこで最近は gas-local を使うという話が
Apps Scriptによるより高度な開発プロセス/More Advanced Development Process with Apps Script // Speaker Deck
にあったのだが、gas-local は mock も使えるテストフレームワークと書かれている割にテストの方法についての記述が見つからない。そこでこっちを追うのはやめて、普通に Node.js 向けの代表的なツールチェインで完結させることにした。
上に挙げた mocha のコマンドで以下のものが揃っている。
- テストランナーは Mocha
- アサーションは PowerAssert
- 構文は Babel を使って ES2015 で
そして JS で test double と言えば Sinon だろう。
こいつで stub out してやれば、まぁなんとかなるだろうと思っていたが、ちょっとハマった。
それは
存在しないオブジェクト、存在しないメソッドは stub out できない
というもの。そらそうか。
確認したバージョンは Sinon 2.3.2
今回試しに作ったコードでは
FetchUrlApp
を使っていたので、
class Sender {
urlFetcher() {
return (typeof FetchUrlApp == 'object') ? FetchUrlApp : {fetch: function(){}};
}
}
として間接的に stub out 可能なオブジェクトを用意することとした。具体的に stub out するコードはこんな感じ。
var sender = new Sender();
sinon.stub(sender, 'urlFetcher').callsFake(function() {
return {
fetch: function(uri, params) {
return params.payload;
}
}
});
何をしているかというと、FetchUrlApp.fetch(uri, params) で POST を行う部分を stub out している。
- urlFetcher() というメソッドで本物の FetchUrlApp かそれを stub out 可能な構造のオブジェクトを得る
- stub out 可能なオブジェクトとは stub out したいメソッドをすでに持っているオブジェクト。ここでは fetch() メソッドを利用する予定なので、Node.js 環境で実行した際には {fetch: function(){}} というオブジェクトを返すようにしている。
てな具合。慣れないとうへぇって感じだし、結局 production 環境で動くことの確認は手動で一度でも動かしてみないとダメなので、本当に全自動というわけにはいかないが、安定して回るようになれば Node.js だけで高速に開発サイクルを回せるので、なかなかよいと思う。
何より、テスト向けに出てきたツールの中に GAS 開発独自のものはないので、GAS 以外のノウハウが活かしやすい。これは大きい。全部見返してみても、セットアップが済んでしまえば、あとは import / export が使えないことに気をつける以外は特別な部分はほぼないはずだ。
これであとは staging と production を切り替えられるような仕組みを用意すれば、普通の Web アプリのように GitHub Workflow でだいたいの開発を回せるはずだ。
よしよし。