THUMBS SHIFT→

このブログは主に親指シフトを用いて書かれています

firebaseをどうテストするか

firebaseをいじってて少しだけ知見がたまってきたので共有します。

前提

ローカルでテストしたいなら、Firebase Local Emulator Suite を使いましょう。 yarn run firebase emulators:startで起動できます。

yarn firebase emulators:exec 'yarn test'ってやると、emulator起動してからテストしてくれます。

設定

これを書いておきましょう。ローカルでの起動でemulatorを使うようになります。create-react-appを使ってるのでNODE_ENVで分岐してますが、別に他のでもいいです。あとポートは自分のに合わせましょう。

const app = firebase.initializeApp(firebaseConfig);
export const db = app.firestore();
if (process.env.NODE_ENV !== "production") {
  db.settings({
    host: "localhost:8080",
    ssl: false,
  });
  app.functions().useFunctionsEmulator("http://localhost:5001");
}

firestore

rulesのテストは簡単です。@firebase/rules-unit-testingを使えば下のようなコードですぐテストできます。(@firebase/testingから改名しました)

import {
  assertFails,
  initializeTestApp,
} from "@firebase/rules-unit-testing";

test("denied if not authed", async () => {
    const notAuthedDB = initializeTestApp({ auth: { uid: undefined } }).firestore();
    const userRef = notAuthedDB.doc("/users/uid");
    await assertFails(userRef.set({ hoge: "hoge" }));
  });

もしrulesを回避して先にテスト用データを用意しておきたいなら、initializeAdminAppを使いましょう*1

firebase.rulesはちゃんと設定している限り自動で読み込まれるので、loadFirestoreRulesbeforeEachで読み込んだりする必要はないはずです。

問題なのが、以下のような関数をテストする場合です。

export const complexTransaction = async () => {
  // 複雑なDB操作...
}

これに関してはスマートな解決策を見つけられていません。一応jestの場合は、

jest.mock("firebase/app", () => {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const { initializeTestApp, firestore } = require("@firebase/testing");
  const mockedApp = initializeTestApp({
    projectId: "pid",
    auth: { uid: "uid" },
  });
  return {
    initializeApp: () => mockedApp,
    firestore: firestore,
  };
});

というようにmockすればなんとかテストできますが、あまり綺麗ではないですね。最悪adminでfirebase.firestoreをemulatorにつなげてくれればいいんですが……。

functions

import * as admin from "firebase-admin";
import * as funcTest from "firebase-functions-test";

const db = admin.firestore();
const tester = funcTest({ projectId: process.env.GCLOUD_PROJECT });
const makeDocumentSnapshot = tester.firestore.makeDocumentSnapshot;
const makeChange = tester.makeChange;

const wrapped = tester.wrap(yourAwesomeFunction);
test("assert Firestore", async () => {
  // arrange
  const roomId = "sefasef";
  const roomPath = `rooms/${roomId}`;
  const beforeData = {
    hoge: "hoge",
  };
  const before = makeDocumentSnapshot(beforeData, roomPath);
  const afterData = {
    hoge: "fuga",
  };
  const after = makeDocumentSnapshot(afterData, roomPath);
  const change = makeChange(before, after);
  // act
  await wrapped(change, { params: { roomId } });
  // assert
  const roomRef = db.doc(roomPath);
  expect((await roomRef.get()).data()).toEqual(afterData);
});

こんな感じでいけます。functionsのテストは比較的簡単です。あと、Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may failと警告がでます。これは正直わからん。。。

*1:initializeTestAppのprojectIdと同じもので初期化する必要があります

firebase emulatorにauthenticationはまだ無いよ

https://github.com/firebase/firebase-tools/issues/1677

しばらくfirebase authをエミュレートする方法探してたけど、上のissueにあるように、まだ実装されてない。

やる気は有るようなので待つしかないなーというかんじ

でもログイン回りのテストどうしようか・・・

VS Code Server for WSL closed unexpectedly. Check WSL terminal for more details. というエラーが出る。

vscode+wsl2でモダン開発しよwってなったけどエラー出た。 ggるとPATHの設定だったり、proxyだったりがでてくるけどこれはWSL2になって改善されたっぽい(?)

PS > wsl
$: cd
~$: ls -a ←これで.vscode-server/がでてくるはず
~$: mv .vscode-server/ vscodeserver.backup
~$: code .

vscode-serverが再設定されるので治った。.backupつけた奴は様子見て消していいんじゃないでしょうか。

AVIOT TE-D01g のペアリング方法

左右分離式なので、結構ペアリングが上手くいかなかったりしてめんどいので、うまくいく経験的なやり方を書きます。

  1. 親機からペアリング解除し、イヤホンと親機の電源を切る(二回効果音が鳴るまでボタン長押しで電源off)
  2. イヤホンを左右ともペアリング状態にする(3回なるまで長押しでペアリング状態)
  3. 両方ともペアリングする。(説明書には片方でいいと書いてあったはず)

これが一番安定する気がします。

ちなみに再ペアリングすると、同じデバイスでも2デバイス扱いになるので、最後から3回目につなげたデバイスがイヤホンのメモリから削除されます。

片方だけペアリングしてもうまくいかないのはなぜ~~~~。

追記

両方電源ON→片方だけペアリング でも上手くいくことが最近判明しました。よかったですね。

Windows 10 EducationでMicrosoft Storeを利用する

大学配布のwinライセンスを使っているのでEducationになっているのですが、大学側で一部の利用が制限されている(?)ようで、レポート中でもwindows updateで強制再起動させられたりしています。

中でも一番困ったのが、Microsoft Storeが使えないことで、待ちに待ったWSL2とwindows terminalを入れられないというもどかしい思いをしていました。調べてみると一応解決したのでのせておきます。これがほかの人の環境でも有効なのかはわかんないです。

docs.microsoft.com

ここを見ましょう。 簡単に言うと、

  1. https://educationstore.microsoft.com/ja-jp/storeに行く。
  2. 学校のために購入タブからアプリを検索。(Ubuntuなど)
  3. アプリの入手をクリック
  4. 「インベントリに追加されました」と出るので、インストールをクリック
  5. Microsoft Storeを開く」をクリック

でできると思います。

注意として、microsoft storeのアカウントなのですが、これは自分のアカウントではなく、大学や会社など、組織のアカウントじゃないとだめです。大学の場合はwinのライセンスもらうときとかに配布されるんじゃないかなと思います。忘れたら・・・うん。

Django+React+GraphQLでSPAを作ってみた感想

Django+React+GraphQLでSPA(なお1ページのみ)を作ってみた感想を書いていきます。なお、趣味グラマーなので仕事で使う観点では見ていません。あと認証処理も実装してないです。

今回の構成

サーバーはHerokuをつかいました。フロントエンドとバックエンドでサーバーを分けます。最初は一緒のサーバーから配信しようとしたんですけど、ルーティングが(ReactのルーティングとDjangoのが混ざって)めんどいので止めました。

使ったライブラリ

  • バックエンド: Django, graphene-django
  • フロントエンド: React, Apollo, (Typescript)

大まかな手順

リポジトリはここ

フロントエンド github.com

バックエンド github.com

つくったのはこれ

https://ararkblog.herokuapp.com/

良さみ

Pythonが便利

まじで声を大にして言いたいんですけど、JSのデータ操作辛すぎる!!!!。特にArrayお前だよ。意味分からん仕様しかないのでPythonを使うと感謝で泣ける。

データ操作が楽

Djangoのモデルが強力なので簡単にGraphQLのレスポンスを書ける。まじで軽く調べてみた感じ、Node系のwebフレームワークはDB操作ほとんど自分でやらなきゃいけない印象があるのでこの点は良い。

graphene-djangoに乗っかれば簡単にページネーションを実装できる。

graphiQLが強い

ブラウザ上でGraphQLのクエリを書いて実行できるgraphiQLっていうのがgraphene-djangoに同梱されてるんですけど、これが便利。簡単なテストをすぐ書ける。

まぁ多分graphiQLは他のツールでも使えると思いますが。

Djangoが管理画面を生成してくれる

Djangoがadminサイトを自動生成してくれるので、データ見てみたり、テストデータ適当に作ったりするのに便利です。他のフレームワークにもあるかどうかはわからない。無い印象。

つらみ

Pythonが動的型

最初Pythonやってた時は、動的型付けサイコー!って感じだったけど、Typescript触ってからは静的型付けサイコー!になったのでPython辛い。補完効かないのが一番つらい。mypy早く進化してくれ・・・。mypyがTypescript並になったらほんとに最強。

あとDjangoの慣習として、モジュールとかメソッドを文字列で指定して、ランタイムで動的に解決するってのがある*1。これをやられるとデプロイはできるけど特定のアクションを行って初めてエラーが出ることがあるので辛い。

文字列じゃなくて直接オブジェクトを指定するようになったりしないのかな?結構冗長になるけど。

ルーティングが辛い

上でもちょっと書いたけど、DjangoでReactRouterとか使おうとすると、DjangoのURLとSPAのURLが衝突するのでめんどくさい。/adminがDjangoのかSPAのURLなのか、判定が面倒くさい。

今回はDjangoとReactでサーバーを分けた。ホントは一緒のサーバーから配信したいけど、SPAのお作法的にはどうなんだろう。あんまりこれについて書いてある資料がみつからなかった。

これはNode系のフレームワークだと解決するのかな?未調査。

使わない機能がおおい

まぁこれはつらみではないけど、テンプレートとビューを使ってないので、大雑把に言って2/3の機能を使ってない。できればミニマムにいきたい。

Pythonの環境構築のベストプラクティスがない

Nodeの環境構築はyarnかnpmを使って簡単にできるけど、PythonはPipenvが死んだせいで戦国時代になりつつある。早く統一されてくれ・・・

結論

バックエンドをNodeにしてもDjangoにしても結局辛みはあるので使いやすい方でやろう。

*1:include("hoge.urls")とか、MIDLLEWARE=["hoge.middleware"]みたいな

Djangoの設定のベタープラクティス

Djangoをデプロイする時の設定です。ベストプラクティスじゃなくて(そのままよりは)ベタープラクティスです

絶対に漏れ、抜けがあるので参考程度に見てください。公式を見るのが最も安全です。

settings.pyの分割

Djangoの本番/開発設定を切り分けるにはsettings.pyを開発用、本番用に分割するのが良いようです。実際DjangoCMSであるWagtailもそのような構成になっています。

普通にDjangoでプロジェクトを作ると、

$ django-admin startproject myprj
$ tree myprj

myprj/
├── manage.py
└── myprj
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

となりますが、Wagtailでは

$ wagtail start myprj
$ tree myprj
myprj/
├── manage.py
├── myprj/
│   ├── __init__.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── dev.py
│   │   └── production.py
│   ├── urls.py
│   └── wsgi.py

というように、共通の設定(base.py)を用意して、開発用(dev.py)と本番用(production.py)で設定を切り替えています。

そもそもなんで開発/本番で設定を切り替えたいかというと、

  • 本番ではDEBUG=Falseにする
  • SECRET_KEYを隠したい
  • ALLOWED_HOSTSを設定する

など、開発/本番で違う点があるためです。

settingsの中身

具体的に、dev.pyは以下のようになっています。

from .base import *

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'o4ei=g0^4cu88sw$@rwuy*gh!4lu7ex74#0t!h^0=fy2k$4-(2'

# SECURITY WARNING: define the correct hosts in production!
ALLOWED_HOSTS = ['*'] 

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'


# ここはよくわかんない。ローカル専用の設定かな?
try:
    from .local import *
except ImportError:
    pass

前述したとおり DEBUG=Trueなのと、ALLOWED_HOSTS=['*']です。 それにSECRET_KEYが直書きですね。

コメントにSECURITY_WARNINGとあるように、この設定は本番で使ってはいけません。また、SECRET_KEYを公にしてもいけません。もしGithubなどに公開してしまったら、別の値に変えた上で、dev.pyをリポジトリから消しましょう*1

ちなみにdev.pyのファイル名についてですが、ここにあるように、local_settings.pyとするのが一般的なようです。

つぎにproduction.pyです

from .base import *

DEBUG = False

try:
    from .local import *
except ImportError:
    pass

これだけ。もはや何もかいてないです。ちゃんと自分で設定しろということでしょう。

  • DEBUG=False
  • SECRET_KEY
  • DATABASE
  • ALLOWED_HOSTS
  • STATIC_ROOT
  • STATIC_URL

とかを設定すれば動くはずです。多分。

なお、Herokuにデプロイする時はdjango-herokuがよしなにやってくれますが、動作を知りたい人は以前の記事で書いたので読んでみてください。

arark.hatenadiary.jp

settingsを使うには

多分分割しただけだとpython manage.py <command>したときにファイルが見つかんないよ的なエラーが出るとおもいます。

manage.pyを以下のように変えてください。

# もともとこうなっているはずなので
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myprj.settings')

# このように設定ファイルの相対パスを指定する
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myprj.settings.dev")

dev/productionはやはり開発/本番で変えるはずなので、try-catchで、dev.pyがあればそっちを使うなどのように条件分岐させてみてください。

ちなみにwsgi.pyやasgi.pyなども同じように設定する必要があります。

*1:それがセキュリティ的に正しいのかはわかりませんが...

Folding@homeに参加しました。手順とトラブルシューティング。

Folding@home(fah)に参加しました。化石と化していた1050tiを使っています。

Folding@homeとは、タンパク質構造予測に必要な計算処理を、世界中の有志による分散コンピューティングで推進するプロジェクトです。 10年以上昔の話になりますが、PlayStation 3を活用したプロジェクトを覚えている方も多いかと思います。

Folding@homeを通じてCOVID-19治療薬の発見に貢献します - Cybozu Inside Out | サイボウズエンジニアのブログ

参加方法

僕はubuntuを使っているので、MacWindowsはあんまりわかんないです。多分インストールして、Foldで出来ると思います。

  1. ここから自分にあったインストーラーをDLする。
  2. Linux系は最低限、fahclientとfahcontrolをDLする。fahviewerはなくても良い。(多分)
  3. statusタブを開いて、Foldをクリックする。

FAQ

client, control, viewerの違いはなんですか?

それぞれが左のソフトウェアに依存しています(多分)。clientがコアソフトウェアで、controlが制御用、viewerでこんなのが見れます。 f:id:arark:20200328194236p:plain

GPUが使われないのですが?

まずドライバをインストールしましょう。

次に

sudo apt install ocl-icd-opencl-dev

してください。

そして、 clientを起動->configure->Expert->Extra Client Optionsの、gpuがfalseになっているならtrueにする->再起動 をやってください。

これでGPUを使ってFoldできるならOK。出来ないならconfigure -> Slots -> Folding Slots-> 追加 ->gpuを試してみてください。

これでも出来ないならわからないです...

GPUのステータスを見たい

nvidia-smi -l 3600

で1時間ごとに使用率とか、温度とかが見れます。

Foldが始まりません

結構中央サーバーにアクセスが集中しているようで、必要なデータをDLするまで時間がかかります。放置してると始まるかも。

ERROR:WU01:FS01:Exception: Could not get an assignmentってエラーがでたりします。

何を計算しているんですか?

解決できません!

公式フォーラムに行きましょう。

ちなみに、

EFLOPSレベルまでいったそうです。

ReactをHerokuに秒速でデプロイしたい

create-react-appで作ったプロジェクトに限った話です!!!!!!!

github.com

ここに書いてあるまんまですが、

APP_NAME=すごいアプリの名前
npx create-react-app $APP_NAME
cd $APP_NAME
heroku create $APP_NAME --buildpack mars/create-react-app
git push heroku master
heroku open

--buildpack mars/create-react-appが一番大事なところなので忘れないように。ちなみに既にあるHerokuアプリでcreate-react-appを使いたい時は、

heroku buildpacks:set mars/create-react-app

です*1

django-herokuは何をやっているのか

django-herokuに頼らず自分で設定していきたいので、まずはdjango-herokuが何をやっているか確認していきます。

github.com

環境変数という語がでてきますが、これはherokuのダッシュボードもしくはCLIから割り当てられる変数で、(おそらく)セキュアです。os.environ[<key>]で取得できます。

49行目 にsettings関数が有りますね。この中を読んでいけばよさそうです。

まず引数ですが、configにはlocals()が渡されるので、つまりはsettings.pyの名前空間です。

53行目からはデータベースの設定のようです。

loggerを飛ばして見ていくと、75行目で大事な設定をしているのがわかります。

 config['DATABASES']['default'] = dj_database_url.config(conn_max_age=conn_max_age, ssl_require=True)

ここでdj_database_url.configという関数がでてきますが、これは環境変数の$DATABASE_URLをURLとか、パスワードとかの部分に分解して、djangoの設定に合うようにパースしてくれています。

これでデータベースは終わりです。

次に92行目からstaticfileの設定をしているようです。

95行目からは以下のようなコードです。

        config['STATIC_ROOT'] = os.path.join(config['BASE_DIR'], 'staticfiles')
        config['STATIC_URL'] = '/static/'


        # Ensure STATIC_ROOT exists.
        os.makedirs(config['STATIC_ROOT'], exist_ok=True)


        # Insert Whitenoise Middleware.
        try:
            config['MIDDLEWARE_CLASSES'] = tuple(['whitenoise.middleware.WhiteNoiseMiddleware'] + list(config['MIDDLEWARE_CLASSES']))
        except KeyError:
            config['MIDDLEWARE'] = tuple(['whitenoise.middleware.WhiteNoiseMiddleware'] + list(config['MIDDLEWARE']))


        # Enable GZip.
        config['STATICFILES_STORAGE'] = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

STATIC_ROOTとSTATIC_URLの設定をして、STATIC_ROOTのフォルダを作っていますね。

そのあとwhitenoise*1をmiddlewareに追加して、STATICFILES_STORAGEもwhitenoiseのものに差し替えています。

110行目からはALLOWED_HOSTSです。

    if allowed_hosts:
        logger.info('Applying Heroku ALLOWED_HOSTS configuration to Django settings.')
        config['ALLOWED_HOSTS'] = ['*']

あーよくない。ALLOWED_HOSTS=['*']にしているのでよくないです。なにが良くないのかはわかんないです。

テスト環境ならこれでも良いでしょうが、本番の場合はちゃんとホストするアドレスを指定しておきたいですね。

114行目からはロガーの設定なので割愛。

151行目以下でSECRET_KEYです。

    if secret_key:
        if 'SECRET_KEY' in os.environ:
            logger.info('Adding $SECRET_KEY to SECRET_KEY Django setting.')
            # Set the Django setting from the environment variable.
            config['SECRET_KEY'] = os.environ['SECRET_KEY']

環境変数から取ってきたSECRET_KEYをconfigに割り当てています。間違ってもSECRET_KEYを公開しないようにしましょう。

これをsettings.pyに持ってくればherokuにデプロイできます。

まとめると

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
MIDDLEWARE = [
    # ...
    'whitenoise.middleware.WhiteNoiseMiddleware',
    # ...
]


DATABASES = {
    'default': dj_database_url.config(conn_max_age=CONN_MAX_AGE, ssl_require=True)
}

SECRET_KEY = os.environ.get("SECRET_KEY")

のようになります。whitenoiseのインストールや、環境変数の割当を忘れないでください。

*1:nginxみたいなもの。静的ファイルを配信する。djangoが静的ファイルを配信するより高速