python×Cloud Functions×FirestoreでAPIを簡単に作ってみる Pt2 テスト編(詳細)


概要

Firestoreのデータ操作を行う関数をローカルでテストするためのあれこれをまとめます。やり方としては、Flaskのrequestオブジェクトを使ってローカルにAPIを作成します。

開発環境

  • 開発環境
    • MacOS
    • python3.7
    • pycharm

新規プロジェクトの作成

pycharmまたはpyvenvコマンドを用いて新規プロジェクトを作成します。画面イメージはpycharmですが、デフォルトで作成されるフォルダに加えて「.auth」「functions」も作成しています。また、ファイルは省略します。

関数の作成

テストする関数はPt1と同じです。functions/に置きます。

functions/getFilteredUser.py

import json
from google.cloud import firestore

# ソースはPt1と同じ
def get_filtered_user(request):

    request_json = request.get_json()
    if request.args and 'name' in request.args:
        request_name = request.args.get('name')
    elif request_json and 'name' in request_json:
        request_name = request_json['name']
    else:
        request_name = ''

    db = firestore.Client()

    query = db.collection('user').where('name', '==', request_name)
    docs = query.get()
    users_list = []
    for doc in docs:
        users_list.append(doc.to_dict())
    return_json = json.dumps({"users": users_list}, ensure_ascii=False)

    return return_json

ローカルにAPIを作る

Cloud FunctionsではFlaskのrequestオブジェクトを使って、リクエスト条件が関数に受け渡しされます。なので、ローカルにFlaskを使ってAPIを作ることで、テスト可能になります。

以下のようなソースを作ります。

localServer.py

from flask import Flask, request, abort, render_template, send_from_directory
# 作成した関数を外部ソースからimport
from functions.getFilteredUser import get_filtered_user

app = Flask(__name__)

# methodにPOST等も明示的に指定(FlaskではデフォルトGETのみのため)
# '/~'がテストするときのパス
@app.route('/get-filtered-user', methods=['GET', 'POST'])
def local_api():
    return get_filtered_user(request)

if __name__ == '__main__':
    app.run()

これでhttp://localhost:5000/get-filtered-userにリクエストを投げると、get_filtered_userが実行されるようになります。
但し、関数がローカルからFirebaseにアクセスするには、秘密鍵を環境変数に読み込ませる必要があります。

秘密鍵の取得・設定

秘密鍵の取得方法は以下のいずれかのページがわかりやすいです。

Cloud FirestoreのデータをPythonで取得する
Firebase公式 - SDK の初期化

取得したファイルのパスを、GOOGLE_APPLICATION_CREDENTIALSの環境変数に設定することでローカルからのFirestoreへのアクセスが可能になります。環境変数の設定は以下のような方法があります。

[方法1] pycharmの「構成の編集>環境変数」から追加
pycharmではこのやり方が使えます。ソースごとに環境変数が設定可能なので、複数のFirebaseプロジェクトを扱う場合でも、bash_profileを書き換える必要がなくなります。

この例では「.auth/firebase_auth.json」という秘密鍵ファイルを読み込ませています。

[方法2] ターミナルから環境変数を設定

MacOS
$ export GOOGLE_APPLICATION_CREDENTIALS="<保存先>/<秘密鍵ファイル>.json"
# .bash_profileに追記する場合 (ターミナルの再起動が必要)
$ echo 'export GOOGLE_APPLICATION_CREDENTIALS="<保存先>/<秘密鍵ファイル>.json"' >> ~/.bash_profile

作成したソースをターミナルからpython sample.pyと実行する場合、このやり方が一番シンプルです。

[方法3] pythonのソースにベタ書き

os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "<保存先>/<秘密鍵ファイル>.json"

ソースの中に書きたい事情がある場合はこれでもいけると思います。

ディレクトリの最終状態

functionsと.authを含めて以下の状態になっているはず。functions/getUser.pyはPt1のソースが残ってるだけなので無くても問題ありません。

ローカルでのテスト

環境変数を設定した状態で、localServer.pyを実行してhttp://localhost:5000/get-filtered-userにリクエストを投げます。

localServer.pyの実行
$ python localServer.py
 * Serving Flask app "localServer" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [04/Aug/2019 14:51:35] "POST /get-filtered-user HTTP/1.1" 200 -

ローカルでの関数テスト
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice"}' http://localhost:5000/get-filtered-user
{"users": [{"age": 19, "name": "Alice", "gender": "female"}]}

これで、Cloud Functionsにデプロイせずともテストできるようになりました。デプロイすると2分ほど待ちますが、この方法だとすぐにテストできます。localServer.pyのコンソールにはprintの内容が出力されるのでデバッグも容易になります。

また、pycharmの場合、pythonの実行とターミナルを並べて実行すれば便利です。

もちろんPostmanを使ってもOKです。

おわりに

Cloud Functionsではいきなりデプロイするのではなく、以下のような流れでコードを作成すると、どこでバグってるかがわかりやすいので、ハマることが少なく開発できると思います。

  1. デプロイしたい関数だけでテスト
  2. ローカルにAPIを作成してテスト
  3. Cloud Functionsにデプロイ

1では、リクエスト条件などをベタ書きで書いてみて、関数が想定通り動くか確認します。2では、GETやPOSTでリクエストが渡された時の動きを確認できます。

今回は、この中の2のやり方を書きました。

Pt3では、3のデプロイをローカルから行う方法を書こうと思います。

参考リンク

How to develop Python Google Cloud Functions
ローカルでの関数の実行
MacでCloud Machine Learning Engineを利用してみる