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

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


 - 前回の記事はこちら
   - [python×Cloud Functions×FirestoreでAPIを簡単に作ってみる Pt1](https://qiita.com/ny7760/items/9cdc3407dfda99c21ae1)
   - [python×Cloud Functions×FirestoreでAPIを簡単に作ってみる Pt2 テスト編（要約）](https://qiita.com/ny7760/items/5519dd3988ceb2326d38)

## 開発環境
- 開発環境
  - MacOS
  - python3.7
  - pycharm

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

<img width="314" alt="スクリーンショット 2019-08-04 16.17.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/0e1a5e5a-b0e2-d4ae-e015-ce150948cb52.png">


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

```python: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を作ることで、テスト可能になります。

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

```python: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で取得する](https://qiita.com/yusukeito58/items/c77feaa25fbbe37e9953)
[Firebase公式 - SDK の初期化](https://firebase.google.com/docs/admin/setup?hl=ja#initialize_the_sdk)

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


[方法1] pycharmの「構成の編集>環境変数」から追加
pycharmではこのやり方が使えます。ソースごとに環境変数が設定可能なので、複数のFirebaseプロジェクトを扱う場合でも、bash_profileを書き換える必要がなくなります。
<img width="588" alt="スクリーンショット 2019-07-17 1.03.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/9412f00b-63c1-934c-12d4-d6ea5bc49045.png">

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

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

```bash:MacOS
$ export GOOGLE_APPLICATION_CREDENTIALS="<保存先>/<秘密鍵ファイル>.json"
# .bash_profileに追記する場合 （ターミナルの再起動が必要）
$ echo 'export GOOGLE_APPLICATION_CREDENTIALS="<保存先>/<秘密鍵ファイル>.json"' >> ~/.bash_profile
```
  作成したソースをターミナルから```python sample.py```と実行する場合、このやり方が一番シンプルです。

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

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

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

## ディレクトリの最終状態
functionsと.authを含めて以下の状態になっているはず。```functions/getUser.py```はPt1のソースが残ってるだけなので無くても問題ありません。
<img width="312" alt="スクリーンショット 2019-08-04 16.23.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/b5f17ffc-a250-4993-acb9-b6328d44f5ad.png">


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

```console: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 -

```


```console:ローカルでの関数テスト
$ 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にデプロイせずともテストできるようになりました。デプロイすると２分ほど待ちますが、この方法だとすぐにテストできます。localServer.pyのコンソールにはprintの内容が出力されるのでデバッグも容易になります。

また、pycharmの場合、pythonの実行とターミナルを並べて実行すれば便利です。
<img width="1280" alt="スクリーンショット 2019-08-04 15.22.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/d2970ef8-d2d6-546e-afd6-bcdb0028f929.png">

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

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

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

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

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

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

 - 次回の記事はこちら
   - [python×Cloud Functions×FirestoreでAPIを簡単に作ってみる Pt3 デプロイ編](https://qiita.com/ny7760/items/7ff05b255aac2e8fa3a3)


## 参考リンク
[How to develop Python Google Cloud Functions](https://www.timvink.nl/google-cloud-functions/)
[ローカルでの関数の実行](https://firebase.google.com/docs/functions/local-emulator?hl=ja)
[MacでCloud Machine Learning Engineを利用してみる]
(https://qiita.com/kai_kou/items/0952c9b28efe19f2f680)