2018年4月24日
自己紹介
ちきさん
Twitter/GitHub/Qiita: @ovrmrw
市ヶ谷のオプトという会社で働いています。初心者です。
今回のGitHubリポジトリ
「Cloud Functions for Firebase」とは
Cloud Functions for Firebase を使用すると、Firebase 機能や HTTPS リクエストによってトリガーされたイベントに応じて、バックエンド コードを自動的に実行できます。コードは Google のクラウドに保存され、マネージド環境で実行されます。独自のサーバーを管理およびスケーリングする必要はありません。
( https://firebase.google.com/docs/functions/?hl=ja より)
何がうれしいのか
詳しくは Cloud Functions で可能な処理 (公式) で。
要するにAWS Lambdaみたいなやつです。 名前が長いのでここから先は Cloud Function と呼びます。
Cloud Functionを書いているときのつらみ
- 実際にクラウド上で動かしてみないとデータが思い通りに流れているかわかりにくい。
- とはいえ**「コード直して」「デプロイして」「手動でイベント発生させて」「ログ確認して」の繰り返しは精神的にも時間的にもつらい。**
そこで オンラインテスト
- **「どうなるはず」を書くより、「実際にどうなったか」**を書く方が手っ取り早い。(個人の意見)
- テストを書くというより、実際に起きた結果を確認しながらアサーションを書いていたらテストを書いていた、みたいな感じ。
- (推奨されている手法かどうかはわからない)
オンラインテスト
オンラインテストの基本形
const Test = require('firebase-functions-test');
describe('.....', () => {
let test;
beforeEach(() => {
test = Test(JSON.parse(process.env.FIREBASE_CONFIG));
});
it('.....', async () => {
// Realtime Databaseの場合
const snapshot = test.database.makeDataSnapshot(value, refPath);
await test.wrap(your_cloud_function)(snapshot);
});
});
(ref: https://github.com/firebase/firebase-functions-test )
itの中身をクローズアップ
const snapshot = test.database.makeDataSnapshot(value, refPath);
await test.wrap(your_cloud_function)(snapshot); // Cloud Functionを発火させる。
-
makeDataSnapshot()
に値を渡してDataSnapshot
型のオブジェクトを作る。 -
wrap()
にCloud Functionを渡してイベント発生をシミュレートするWrapped Functionを作り、その引数にDataSnapshot
を渡す。 - これによりCloud Functionがイベント駆動する様子をローカルで再現できる。
オンラインテストの例 (Database => PubSub)
- Databaseの
onCreate
イベントをトリガーにする。 - Cloud Function発火後、Subscriberが
pull
してTopicにメッセージが送られたことをアサーションする。
pubsub.spec.js
import * as Test from 'firebase-functions-test';
import { PubsubHelper } from '../../testing/helpers';
import { pubsub, topicName } from '../../src/pubsub';
jest.setTimeout(1000 * 30);
describe('pubsub', () => {
let test;
let pubsubHelper;
const subscriptionName = 'sample-subscription__test';
beforeEach(() => {
test = Test(JSON.parse(process.env.FIREBASE_CONFIG));
pubsubHelper = new PubsubHelper();
});
beforeEach(async () => {
// initialize testing environment
await pubsubHelper.deleteSubscription(subscriptionName);
await pubsubHelper.createSubscription(subscriptionName, topicName);
});
it('Topicにメッセージが送られる。', async () => {
const value = { bar: 1 };
// trigger
const snapshot = test.database.makeDataSnapshot(value, '');
await test.wrap(pubsub)(snapshot);
// pubsub message assertion
const messages = await pubsubHelper.pullMessages(subscriptionName);
expect(messages.length).toBe(1);
const firstMessage = JSON.parse(messages[0].message.data.toString());
expect(firstMessage).toEqual(value);
});
});
オンラインテストの Tips
テスト用のプロジェクトを別に作る
- テスト用のプロジェクトにはCloud Functionをデプロイしてはいけない。
- なぜならオンラインテストのとき、イベントに対してローカルのFunctionとデプロイしたFunctionの両方が動いてしまい、まともな結果が得られなくなるから。(これで超嵌った)
- なのでデプロイせずにテストするためにプロジェクトを別に用意する必要がある。
(ref: Firebaseプロジェクトを本番用とテスト用で使い分けたい)
Cloud Functionのトリガーとなる副作用は(必要なら)自前で発生させる
const snapshot = test.database.makeDataSnapshot(value, refPath);
await snapshot.ref.set(snapshot.val()); // <==(必要なら)事前にDatabaseに書き込む。
await test.wrap(your_cloud_function)(snapshot); // Cloud Functionを発火させる。
- firebase-functions-test を使うとCloud Functionをトリガーすることはできるが、あくまでもイベントが発生したことをシミュレートするだけで実際にイベントは起きていない。(ちゃんと理解してないだけかも...)
- 例えばDatabaseの
onCreate
イベントを発生させることはできるが、実際にDatabaseには書き込まれていない。 - なのでCloud Functionの中でデータを利用・更新する場合は、事前に書き込みをしておく必要がある。
Jestのタイムアウトを長めにする
jest.setTimeout(1000 * 30);
describe('.....', () => {
// テスト
});
- Jestのデフォルトタイムアウトは5秒。
- オンラインテストはクラウド上で副作用を発生させるので5秒では終わらないことが多い。(特にPubSub)
-
it
だけタイムアウト時間を変更してもbeforeEach
でタイムアウトしたりするので全体に適用できるjest.setTimeout()
を使うと便利。
ちょっと待たないとTerminalにログが残らない (ときがあった)
it('.....', async () => {
const snapshot = test.database.makeDataSnapshot(value, refPath);
await test.wrap(your_cloud_function)(snapshot);
await new Promise(resolve => setTimeout(resolve, 1000)); // ちょっと待つ
});
- Cloud Functionを実行した後、ログを全てTerminalに表示させるためにちょっと待つ。
- (再現できなかったけど待たないとログが残らないときがあった。。。)
まとめ
メリット
- 「どうなるはず」ではなく「実際にどうなったか」をアサーションしているので、本番に適用しても問題なく動くことが期待できる。
- 実際に流れている値をconsole.logで確認できる。
デメリット
- 全てをモックするオフラインテストに比べれば単純に時間がかかる。
- クラウド上で動作させるので、たまに通るはずのテストが落ちることがある。
うれしいこと
- 作業効率は上がる、と思う、たぶん。
- 少なくともログを確認するためにデプロイして手動でイベント発生させるのを繰り返すという不毛な戦いは避けられる。
- console.logデバッグをする感覚でコードを書ける。
今朝知ったこと ←NEW!
- 公式のドキュメントが4月20日に更新されていてそっちにだいたい書いてあった。