Firebase emulator がリリースされていて とても開発がはかどりました。
まだ情報がすくないのでPythonからの利用方法をまとめます。
Firebase emulator
firestore をメインのDBとして使用しているので、単体テストで色々なテストデータを登録して作成するのに、ローカルでemulator が動かせると便利です。
下記のような仕組みが準備されているので、まずローカルにemulatorをInstallする
開発言語自体はPythonを使いたいのですが、emulatorは node, javaで動いているので事前にインストールしておく必要があります。
% node --version
v16.13.2
% npm install -g firebase-tools
% firebase login
Already logged in as {$USER}
-
firebaseのプロジェクトを選択時に下記のようなエラーがでることがあります
% firebase projects:list ✖ Preparing the list of your Firebase projects Error: Failed to list Firebase projects. See firebase-debug.log for more info.
表示されているとおりfirebase-debug.log を開いてみると下記のError表示
Error: HTTP Error: 401, Request had invalid authentication credentials. Expected OAuth 2 access token
login できているって表示されてたのに、なぜ!!と思いながら上記のエラーメッセージでググると下記のパラメータで強制的に再認証シーケンスを走らせることで対応できるとのことです。
% firebase login --reauth --no-localhost
-
emulatorの設定
firebase init emulators
- emulator実行
% cd firebase_emulator
% firebase emulators:start
i emulators: Starting emulators: firestore, storage
i firestore: Firestore Emulator logging to firestore-debug.log
i ui: Emulator UI logging to ui-debug.log
┌─────────────────────────────────────────────────────────────┐
│ ✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://localhost:4000 │
└─────────────────────────────────────────────────────────────┘
┌───────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├───────────┼────────────────┼─────────────────────────────────┤
│ Firestore │ localhost:8090 │ http://localhost:4000/firestore │
├───────────┼────────────────┼─────────────────────────────────┤
│ Storage │ localhost:9199 │ http://localhost:4000/storage │
└───────────┴────────────────┴─────────────────────────────────┘
Emulator Hub running at localhost:4400
Other reserved ports: 4500
Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
- Firebase 単体テスト: https://firebase.google.com/docs/rules/unit-tests?hl=ja#rut-v2-common-methods
- vscode のPython開発設定情報参考1:https://atmarkit.itmedia.co.jp/ait/articles/2107/16/news029.html
- vscode のpython開発設定情報参考2 : https://dorapon2000.hatenablog.com/entry/2020/04/29/152251
python からemulatorに接続する
以下のように OSの環境変数にlocal emulator に設定することで
通常通りfirebase admin sdk を使用すると アクセス先がローカルのfirebase emulator になる
- この情報が公式手順上明記されておらず最初はできないのかとおもいました。
- まだemulator 自体が、βバージョンということなので今後のドキュメント含めた拡張に期待します
- 注意点として本番のfirestoreを誤って上書きしてしまわにように、テストのときのProject,ルートドキュメントを別の名前にしておいたほうが良いと思います。
- Localや、GCP上でインスタンスが生成されたままだとfirebase_admin.initialize_appで既にあるとエラーになります。下記のように_appsのlenを調べて初期化必要かどうかを判定しています
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
os.environ["FIRESTORE_EMULATOR_HOST"]="localhost:8090"
os.environ["GCLOUD_PROJECT"]="my_project"
cred = credentials.Certificate(self.config['firebase']['certjson'])
if (not len(firebase_admin._apps)):
firebase_admin.initialize_app(cred)
self.db = firestore.client()
pytest
例えば、pytest上で、emulator をつかって、テスト条件を一定にするために最初にdocument, collection をクリアにするというようなコードは以下のように書くことができます。(emulatorでも普通のfirestoreの使い方と変わりません。)
def setup_method(self,method):
print('method={}'.format(method.__name__))
delete_collection(self.db.collection(u'hoge').document(u'fuga1').collection(u'foo'),16)
delete_collection(self.db.collection(u'hoge').document(u'fuga2').collection(u'baa'),16)
doc_ref = self.db.collection(u'hoge').document(u'fuga1')
doc_ref.set({
u'key1': False,
u'key2': True
})
doc_ref = self.db.collection(u'hoge').document(u'fuga2')
doc_ref.set({
u'key1' : False,
u'key2' : True
})
- Test例:
以下は、ab, cd というデータが事前に登録されていた場合正しく収集できるかという関数のテスト例です。
def test_idea_collection(self):
col_ref = self.db.collection(u'hoge').document(u'fuga1').collection(u'foo')
timestr1 = '2022/2/3 16:48:11'
time1 = datetime.strptime(timestr1 + '+0900', '%Y/%m/%d %H:%M:%S%z')
timestr2 = '2022/2/6 16:48:11'
time2 = datetime.strptime(timestr2 + '+0900', '%Y/%m/%d %H:%M:%S%z')
col_ref.add({
u'author': 'x-man',
u'content':'ab',
u'createdAt':timestr1
})
col_ref.add({
u'author': u'x-woman',
u'content':'cd',
u'createdAt':timestr1
})
collectresult,lastupdate = self.main_target.collect_foo('fuga1')
assert collectresult.count('ab') == 1
assert collectresult.count('cd') == 1
assert collectresult.count('ef') == 0
- test 実行例
- emulator, pytestがローカルでInstallされていれば以下のようにテスト実行を確認できます
% pytest test_hogehoge.py
========================================================================= test session starts =========================================================================
platform darwin -- Python 3.9.0, pytest-7.0.0, pluggy-1.0.0
rootdir: /Users/hoge/src
collected 13 items
test_hogehoge.py ............. [100%]
========================================================================= 13 passed in 20.66s =========================================================================
test 実装時の参考
-
Test setup, tear down
- Test全体のSetup,Teardown、 TestごとのSetupをどう書くかが効率的で安定したテストを書く肝になる。
- https://udemyfun.com/python-pytest-setup-teardown/
-
vscode でのpytest 実行方法