- pgmock は、ユニットテストとE2Eテスト向けのインメモリPostgreSQLモックサーバーで、外部依存なしにNode.jsとブラウザ上でWebAssemblyとして実行される
node-postgres ユーザーは、ポートを開かない設定オブジェクトを受け取って接続でき、この方式はブラウザでも動作する
- ブラウザではWebアプリがTCPポートを開けないが、
PostgresMock.createSocket と node-postgres の設定を利用できる。バンドラーが静的importを解析すると、任意のNode.jsモジュールに関する警告が出る場合がある
- 実装は現在、x86エミュレーター内でPostgreSQLサーバーを実行する方式を採用しており、テストと本番環境間の動作差異の防止を性能より優先している
- 長期的には、ネイティブPostgreSQL WASMフォークが成熟すれば両方式を提供し、その後ネイティブWASMをデフォルトに切り替える計画がある
pgmockが提供するもの
- pgmock は、ユニットテストとE2Eテスト用のインメモリPostgreSQLモックサーバー
- 外部依存は不要で、Node.jsとブラウザの両方で WebAssembly 内で実行される
- npmでインストールできる
npm install pgmock
基本的な利用フロー
- インメモリサーバーは
PostgresMock.create() で作成し、listen(5432) で接続文字列を受け取れる
import { PostgresMock } from "pgmock";
const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
node-postgres を使う場合、mock.getNodePostgresConfig() がポートをlistenせずに接続できる設定オブジェクトを提供する
- 作業が終わったら、リソースを解放するために
mock.destroy() を呼び出す方法が推奨される
mock.destroy();
ブラウザ対応とpgliteとの違い
pgmock はブラウザ環境を完全にサポートする
- WebアプリはTCPポートを開けないが、
PostgresMock.createSocket と node-postgres の設定を利用できる
- バンドラーがimportを静的解析すると、任意のNode.jsモジュールがないという警告が出る場合があり、Webpack設定例は
examples/web-demo/next.config.mjs にある
- ブラウザでデータベースだけを実行したい場合は pglite を検討できる
- pgliteはより高速で軽量だが、機能セットは限定的
pgmock は、テスト環境で求められる本番PostgreSQLとの機能同等性を目標に設計されている
WebAssemblyでPostgreSQLを実行する方式
- WebAssemblyでPostgreSQLを実行する方式は2つある
- ネイティブWASMフォーク方式はより高速でメモリ使用量もはるかに少ないが、シングルユーザーモードのみをサポートし、接続と拡張をサポートしていない
pgmock は現在x86エミュレーター方式を使用している
- テストと本番環境間の不一致を防ぐことが目的
- テストでは通常、性能が大きな問題にならないため
- 中期的には、ネイティブPostgreSQL WASMフォークが成熟すれば、両方のオプションを提供する計画
- その後はネイティブWASMをデフォルトに切り替える予定で、
PostgresMock.subtle 内部API以外には大きなbreaking changeはあまり多くないと見込んでいる
既存のブラウザPostgreSQLプロジェクトとの違い
pgmock はJavaScriptランタイム内で完全な機能互換性を提供し、通信のためにネットワークプロキシへ依存しない
- JavaScriptでネットワークスタックをシミュレートして実際のネットワークのように動作させ、raw socketアクセスを許可しないプラットフォームでもTCP接続をシミュレートできる
拡張可能性と関連プロジェクト
- 他のDockerイメージやデータベースも理論上は実行可能だが、テストはされていない
- 関連する実装と基盤プロジェクトとして以下が挙げられている
1件のコメント
Hacker News のコメント
数か月にわたって会社で インメモリ Postgres 版を作っていて、本番データベースと機能的に同等になっています。
利点は、外部プロセスやプロキシが不要なことです。プラットフォームが WASM を実行できれば、Node.js やブラウザでも pgmock を動かせますし、モックデータ入りの新しいデータベースを作るのも JavaScript オブジェクトを作るくらい簡単です。
pgmock をオープンソース化するきっかけになった pglite とは少し違います。pgmock は元の Postgres を x86 エミュレータ内で実行し、pglite は Postgres のフォークをネイティブ WASM に直接コンパイルしているため、より高速で軽量です。
ただし pglite はシングルユーザーモードと一部の拡張機能しかサポートしていないため、通常の Postgres クライアントでは接続できません。これは E2E テストではかなり重要です。
理論上は、WebAssembly プラットフォーム上で任意の Docker イメージを実行できるように変更できるはずですが、見てみたい具体的な対象があるか気になります。
複数接続モードを追加する方法はいくつか考えていますが、少し時間がかかりそうです。PGlite にはシングルユーザーモードに関連する他の制限もあり、たとえば pg_notify はまだサポートしていませんが、これも修正する予定です。
一方でこのプロジェクトは実際の Postgres にはるかに近いので、そのまま動く可能性が高いです。こうしたインメモリ Postgres プロジェクトは、テスト実行時間を 4分の1以下に短縮できそうで、テスト分野で大きな可能性があります。
PGlite に取り組んでいる者としての考えです。
最近、クライアント側で FFMPEG/SoX パイプラインを動かしたかったのですが、依存関係が多すぎて Emscripten で簡単に再コンパイルするのが難しかったです。このアプローチがそうしたケースにも役立つのか気になります。
リレーショナル機能のおかげで、通常リレーショナルデータベースに入っている豊富なドメイン固有メタデータを追加し、一緒にクエリできます。
select foo();を実行するとError.captureStackTrace is not a functionが出ます。Linux の Firefox 124.0.2 で発生しました。単に Postgres のファイルを ramdisk に置いて実行すればよいのでは、と思いました。
追記: ブラウザ/Node 環境で実行できるので、テストが作成・更新・削除できるようです。自分があまりにバックエンド開発者なので、一般的な開発環境と比べた利点がよく分かりません。どこで、いつ、どのようにより良いのか説明してもらえるとうれしいです。
WebAssembly にした理由は、プラットフォーム、アーキテクチャ、ブラウザやエッジ環境まで含めて動作をより移植性高くそろえられ、Docker すら不要な外部依存なしの構成にできるからです。
エミュレータはすでに起動済みの状態をそのままブートできるため、実データベースや Docker コンテナを立ち上げるよりも、エミュレートされたデータベースの起動のほうが速いです。ただしこれは設計目標というより、運よく得られた効果に近いです。
https://testcontainers.com/ のようなものを使えばよいのでは、と思います。コンテナエンジンが外部依存であることが、そんなに悪いことなのか疑問です。
そこにモックを押し込んだ瞬間に単体テストになります。効果はありますが、同じものではありません。E2E の核心の一つは、モックがないからこそテストが正確だと分かる点です。これは Postgres をテストしているのではなく、毎回このシステムをテストしているということです。
組み込み、軽量、低性能システム向けの PG を作っているなら、遅い実際の E2E テストの前の検証テストとしては筋が通ります。そういう用途は私にもあります。
それ以外では、素晴らしいプロジェクトであり、PG shim が必要なときに使えるツールに見えます。
Postgres では savepoint を使っていますが、ramdisk 上でもそれほど速くありません。
以前はテストでさまざまなカスタムの 偽インメモリサーバーを動かしていました。最近は https://testcontainers.com で実物を実行しています。
Prisma/Node.js 開発者がローカル開発用に「缶詰の Postgres」だけを求めているなら、最近出た PGlite のサーバー版である pglite-server のほうがよいかもしれません: https://github.com/kamilogorek/pglite-server
より高速で、ファイルシステムにデータを保持できますが、利用が多いときは x86 エミュレータ全体を使う E2E テストサーバーより安定性は低いです。pglite-server はメモリを 150MB しか使わない一方、pgmock-server は 830MB 使いました。
dotenv で新しい
.env.localをチェックアウトし、すべての nextjs/prisma のpackage.json実行スクリプトのDATABASE_URLを変更すればよいです。DATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject""db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"どんなプロジェクトにも非常に簡単に組み込めますし、Neon がこの領域を支援しているのも理解できます。
水を差したいわけではありませんが、私はこれを使うつもりはありません。
単純なアプリケーションなら動くかもしれませんが、デッドロックのリスクがあったり、データベースの形に依存していたりするなど複雑性が増すと、小さな挙動の違いでも致命的な問題に発展し得るため、価値は下がります。
最近はリソース制約のある E2E 環境を好んでいます。ローカルのテストランナーが、誰かがひどく非効率なコードを書いたときに壊れる機会を与えてくれるからです。
また、数秒後にデータベースをスナップショット化し、そのスナップショットをテストパーティションに配布する方法は非常に高速で、テストスイートから何分も削減できたことが何度もあります。
興味深いアイデアで、良い学習経験にはなるでしょうが、対象ユーザーは限られると思います。
タイトルが少し紛らわしいです。「会社で作った」ということなら、会社のリソースを使ったという前提で、このプロジェクトの知的財産権は雇用主にあるのではないかと思います。
だとすると、技術的にオープンソースとして公開してよいのか気になります。
Copyright 2024 Stackframe.とあります。作者は Stackframe で働いているようです。これは H2 の Postgres 互換モードと比べるとどうなのか気になります。
かなりいいですね。答えられるなら、いくつか気になります。
会社でこのプロジェクトを作ることになったきっかけは何だったのか、Docker コンテナで Postgres を実行するのが遅すぎたのかが気になります。
pgmock をフローに統合する前後で、E2E テスト用の CI 構成がどう変わったのかも知りたいです。
このソリューションへ移行する過程が難しかったのかも気になります。
本番データをダンプして機密データをすべて除去し、ログテーブルのような不要なテーブルは truncate すれば、開発用の良いコピーができます。
それを開発、QA、E2E などに複製すればよいです。E2E に必要なのはまさにその拡張、トリガー、関数、ビュー、インデックス、データです。
普通に Docker を使って、別のテスト用データベースを用意すればよいのではないかと思います。
Elixir ではそうしていて、テストフレームワークが各テストをトランザクションで包み、分離のためにロールバックします。このアプローチの利点が何なのか分かると興味深いです。