Node.js で BigQuery を使ったコードの自動テストを記述する
Sunday, December 18, 2022 11:15:00 AM
BigQuery へクエリするコードを書くとき、どうしていますか? ORM を使って RDB を使うコードを書いている場合などは、 SQLite などを使って UnitTest を書いていることもあるでしょう。 BigQuery についても、何かそういったことができないかな?と思い、調べていました。
BigQuery Emulator
そんなとき、ちょうど BigQuery Emulator の存在を知り、試してみることにしました。
作者の goccy さんのスライドです。
GitHub の README によると Docker コンテナでも動かせるようですので、私たちはそれを利用することにしました。
$ docker pull ghcr.io/goccy/bigquery-emulator:latest
コードのサンプルとしては Python と Golang での書き方は提供されていたので、 Node.js でも何とかなるんじゃない?… と思いやり始めたのです。
Node.js – BigQuery Emulator
Node.js で BigQuery を利用するには、公式のクライアントをインストールします。
$ npm i @google-cloud/bigquery
接続の方法は Python のクライアントを見る限り、
- エンドポイント
- プロジェクトID
- 匿名接続(AnonymousCredentials)
を指定すれば良さそうです。
client_options = ClientOptions(api_endpoint="http://0.0.0.0:9050")
client = bigquery.Client(
"test",
client_options=client_options,
credentials=AnonymousCredentials(),
)
Node.js だとこんな感じでしょうか?
new BigQuery({
projectId: 'test',
apiEndpoint: 'http://0.0.0.0:9050',
credentials: /* 何を指定すれば良いんだ? */
});
ここで、 credentials
の指定に AnonymousCredentials
みたいなものが無いことに気がつきます。
JavaScript 以外のクライアント、上記の Python や Go には、匿名接続のオプションがあるようです。
現時点の credentials に指定できるのは、公式ドキュメントによると
credentials?: CredentialBody | ExternalAccountClientOptions;
だけです。
ExternalAccountClientOptions
は外部のアカウント連携を使った認証をする場合のオプションになっています。
@google-cloud/bigquery のコードを調べる
クライアントの言語違いで接続が変わるわけではないので、 BigQuery のサーバー側は匿名接続できるようになっているが、クライアント側の実装がサポートしていない、ということは想定できるでしょう。 こういうときは、クライアントのソースコードを調べるしかありません。 正面突破は無理でも、何かハックできる方法があるかもしれません。
BigQuery クラス
GitHubのリポジトリGoogle BigQuery: Node.js Clientです。 まず、 BigQuery クラスのコンストラクタを調べます。
このあたりで、オプションを作り直して親クラスのコンストラクタを呼び出すみたいです。
const config = {
apiEndpoint: options.apiEndpoint!,
baseUrl,
scopes: ['https://www.googleapis.com/auth/bigquery'],
packageJson: require('../../package.json'),
autoRetry: options.autoRetry,
maxRetries: options.maxRetries,
};
if (options.scopes) {
config.scopes = config.scopes.concat(options.scopes);
}
super(config, options);
credentials
は options
の中に入ったままなので、 BigQuery 特有のオプションを config
に置き換えて親クラスである @google-cloud/common/Service
を呼び出す感じでしょうか。
Service クラス
Service
クラスは @google-cloud/common
という共有パッケージにあるので、そこのリポジトリを調べます。
Serviceクラスのコードです。
このあたりで、リクエストコンフィグを作って、ユーティリティクラスのクレデンシャルファクトリを呼び出すみたいです。
const reqCfg = extend({}, config, {
projectIdRequired: this.projectIdRequired,
projectId: this.projectId,
authClient: options.authClient,
credentials: options.credentials,
keyFile: options.keyFilename,
email: options.email,
token: options.token,
});
this.makeAuthenticatedRequest =
util.makeAuthenticatedRequestFactory(reqCfg);
this.authClient = this.makeAuthenticatedRequest.authClient;
this.getCredentials = this.makeAuthenticatedRequest.getCredentials;
なるほど、なるほど。
makeAuthenticatedRequestFactory
続いて、 makeAuthenticatedRequestFactory を見てみましょう。
コードには関数定義がいろいろあって、最終的には makeAuthenticatedRequest
という関数をファクトリメソッドは戻すようです。
ふむふむ。
const mar = makeAuthenticatedRequest as MakeAuthenticatedRequest;
mar.getCredentials = authClient.getCredentials.bind(authClient);
mar.authClient = authClient;
return mar;
つまり BigQuery
クラスのインスタンスを生成すると、認証クライアントまでは生成するけど、接続などにはいかないことがわかります。
続いてファクトリメソッドの引数を調べてみましょう。
興味深いオプションが2つありますね。
認証エンドポイントをカスタマイズできるようです。 では、ファクトリでは、このオプションをどうやって使っているのか見てみましょう。
ここにありました。
const authorizeRequest = async () => {
if (
reqConfig.customEndpoint &&
!reqConfig.useAuthWithCustomEndpoint
) {
// Using a custom API override. Do not use `google-auth-library` for
// authentication. (ex: connecting to a local Datastore server)
return reqOpts;
} else {
return authClient.authorizeRequest(reqOpts);
}
};
コメントを読んでください。
ex: connecting to a local Datastore server
「例えば、ローカルのサーバーとかに接続するときな」って、今回の用途じゃないですか!
どうやったら指定できるのか?
さて、ここまでの調査を振り返りましょう。
- BigQuery クラスで Service クラスのコンストラクタを呼び出す
- Service クラスでリクエストコンフィグを作って、ユーティリティクラスのクレデンシャルファクトリを呼び出す
BigQuery クラスからのオプションでは customEndpoint
を指定する方法がない!という結論。
ちょっと待てよ?
サービスクラスで生成したファクトリどこに入れてたっけ?
this.makeAuthenticatedRequest =
util.makeAuthenticatedRequestFactory(reqCfg);
this
(=== BigQuery クラスのインスタンス) ですね。
サービスクラスの定義を見てみましょう。
makeAuthenticatedRequest: MakeAuthenticatedRequest;
private じゃないってことは、オーバーライドできるじゃないですか。
ハックする
import { BigQuery } from '@google-cloud/bigquery';
import { util } from '@google-cloud/common';
let bigQuery: BigQuery;
beforeAll(() => {
const options = {
projectId: 'test',
apiEndpoint: 'http://0.0.0.0:9050',
baseUrl: 'http://0.0.0.0:9050',
scopes: ['https://www.googleapis.com/auth/bigquery'],
packageJson: require('@google-cloud/bigquery/package.json'),
customEndpoint: true,
};
bigQuery = new BigQuery(options);
bigQuery.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(options);
});
options の基本項目は、 BigQuery クラスのコンストラクタで指定されていた項目をそのまま流用しています。
それに customEndpoint
を指定して makeAuthenticatedRequestFactory
を呼び出しインスタンス変数の値を上書きします。
うまくいきましたよね!
これで Node.js の UnitTest から BigQuery Emulator を使ってクエリのテストコードを書くことができるようになりました。 BigQuery Emulator があって良かった!