ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Electric 集成测试指南:基于 lux 的多组件端到端测试体系解析

Electric 集成测试指南:基于 lux 的多组件端到端测试体系解析 Electric 集成测试指南基于 lux 的多组件端到端测试体系解析【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric导读本文以 integration-tests/README.md 为主线系统讲解 Electric 开源仓库agent platform built on sync中集成测试套件的搭建方法、执行方式与底层设计。你将掌握如何用 lux 测试框架编排 PostgreSQL、Electric 同步服务、PgBouncer 连接池、OpenTelemetry Collector 等多个组件理解宏抽象、健康检查、滚动部署与只读服务等核心测试模式并学会把整套体系复用到自己的多组件端到端测试中。一、为什么需要集成测试单测之外的“多组件编排”场景在 packages/sync-service 中单元测试可以覆盖同步服务的纯逻辑与内部状态机但 Electric 的真实价值在于把 PostgreSQL 的逻辑复制logical replication、HTTP Shape 协议、连接池、可观测性等子系统联动起来。这类跨进程、跨容器的行为只有在真实的数据库和真实的网络环境下才能被验证。于是仓库在 integration-tests 目录下维护了一套独立的集成测试套件。它不直接调用 Elixir 测试框架而是引入lux——一个用于“设置并编排多个组件”的终端驱动测试工具。正如 integration-tests/README.md 所述Were using lux to run integration tests that require setting up and orchestrating multiple components.lux 的特点是面向终端的场景化测试每个.lux文件就是一份可执行的脚本剧本通过声明式的宏、正则断言和超时控制驱动真实的进程与 Docker 容器模拟用户在终端中的完整操作链路。二、环境准备与一键构建两条核心命令integration-tests/README.md 给出了完整的使用流程共两步1. 首次准备make在 dev 机器上第一次运行前先执行一次make查看 integration-tests/Makefile其目标lux完成以下工作LUX_BIN$SCRIPT_DIR/lux/bin/lux lux: git clone https://github.com/hawk/lux.git \ cd lux \ git checkout 629e45c59a352ec49e0b51dde95d4247b579bfa9 \ autoconf \ ./configure \ make将 lux 源码克隆到integration-tests/lux/目录检出固定 commit629e45c...保证测试工具链的可复现性通过autoconf ./configure make从源码编译出lux/bin/lux二进制。2. 执行全部测试./run.sh./run.shintegration-tests/run.sh 的实现要点如下#!/usr/bin/env bash set -e SCRIPT_DIR$(cd -- $(dirname -- ${BASH_SOURCE[0]}) /dev/null pwd) LUX_BIN$(command -v lux || echo $SCRIPT_DIR/lux/bin/lux) if [[ ! -e ${LUX_BIN} ]]; then echo no lux binary available exit 1 fi LUX$LUX_BIN --multiplier${TIMEOUT_MULTIPLIER:-1000} $LUX ${:-tests/*.lux}优先使用 PATH 中的luxcommand -v lux命中则直接使用否则回退到make构建出的integration-tests/lux/bin/lux超时倍增器--multiplier${TIMEOUT_MULTIPLIER:-1000}让所有测试中的超时值乘以系数。在 CI 等慢速环境下可通过环境变量TIMEOUT_MULTIPLIER放大超时避免偶发慢速导致误报默认全量执行${:-tests/*.lux}意味着不带参数时运行tests/目录下所有.lux测试文件你也可以传入单个文件路径做定向调试例如./run.sh tests/rolling-deploy.lux三、支撑组件与脚本基础设施integration-tests/下除了 README 提到的两个命令还配套了一系列支撑脚本与共享文件构成完整的多组件测试平台路径作用integration-tests/scripts/electric_dev.sh以开发模式启动 Electric 同步服务iex -S mix注入集成测试所需环境变量integration-tests/scripts/clean_up.sh清理测试运行产生的_storage存储目录integration-tests/scripts/reset_wal.sh生成随机 WAL 位置并用pg_resetwal重置作为 PG 容器 initdb 脚本integration-tests/scripts/init_db.sh执行$INIT_DB_SQL环境变量中的初始化 SQLintegration-tests/scripts/gen_certs.sh生成用于 SSL 连接测试的根证书与服务端证书integration-tests/scripts/pg-entrypoint-ssl.sh以 SSL 模式启动 PostgreSQL 的入口脚本integration-tests/support_filespostgresql_ssl.conf、pg_hba.conf、server.crt、server.key、root.crt、root_otherhost.crt等测试固定文件integration-tests/test_utils/connection_manager_ping.exs周期性 ping 连接管理器的测试辅助 GenServerintegration-tests/tests/_macros.luxinc全部测试共享的 lux 宏定义文件1. 启动同步服务electric_dev.sh#!/usr/bin/env bash set -e SCRIPT_DIR$(cd -- $(dirname -- ${BASH_SOURCE[0]}) /dev/null pwd) cd $SCRIPT_DIR/../../packages/sync-service MIX_TARGETapplication \ ELECTRIC_STORAGE_DIR$SCRIPT_DIR/../_storage \ ELECTRIC_REPLICATION_STREAM_IDintegration \ iex -r $SCRIPT_DIR/../test_utils/*.exs $ -S mix关键点cd到 packages/sync-service 并在该目录下运行mix即编译并启动真实的同步服务而非 mockELECTRIC_REPLICATION_STREAM_IDintegration让本套测试使用独立的复制流标识replication stream id避免与开发/生产数据互相干扰ELECTRIC_STORAGE_DIR指向integration-tests/_storage测试间的持久化数据集中在可清理的目录中iex -r预先加载test_utils/*.exs辅助模块其中就包含下文的ConnectionManagerPing。2. 连接管理器监控器connection_manager_ping.exsintegration-tests/test_utils/connection_manager_ping.exs 是一个以GenServer实现的定时探针默认每100msping_interval调用一次Electric.Connection.Manager.ping/1若响应超过500msping_response_time记录Logger.error(Connection manager took too long to respond to ping: ...)它被_macros.luxinc中setup_electric_shell宏在服务启动后自动拉起专门用于在集成测试期间持续监测连接管理器的响应性——这正是db-connection-scaledown.lux、insufficient-connection-resources.lux等场景判断资源压力的依据。3. WAL 重置reset_wal.sh逻辑复制测试需要从任意 WAL 位置开始因此 integration-tests/scripts/reset_wal.sh 在容器初始化阶段被挂载为/docker-entrypoint-initdb.d/initdb-reset_wal.sh随机生成 timeline id1~3与 32 位十六进制的 log segment / offset通过pg_ctl stop停库后执行pg_resetwal -l $wal_pos $PGDATA再pg_ctl start重启支持通过环境变量ELECTRIC_PG_START_WAL显式指定起始 WAL 位置。这为crash-recovery.lux、replication-lifetime.lux等需要“从指定 LSN 重建复制”的测试提供了可控起点。四、宏抽象层_macros.luxinc的组件编排原语所有.lux测试文件第一行几乎都是[include _macros.luxinc]该文件定义了整套测试的全局变量与宏是理解集成测试如何“编排多组件”的关键。1. 全局变量[global PS1SH-PROMPT:] [global fail_pattern(?i)error|fatal|no such] [global pg_docker_networkelectric_integration_pg_net] [global shape_url_port3000] [global pg_container_name] [global pg_host_port54331] [global pg_pooler_host_port64331] [global database_urlpostgresql://postgres:passwordlocalhost:$pg_host_port/electric?sslmodedisable] [global pooled_database_urlpostgresql://postgres:passwordlocalhost:$pg_pooler_host_port/electric?sslmodedisable] [global otel_collector_container_nameelectric_integration_otel_collector]PS1与fail_pattern分别定义 shell 提示符与全局失败匹配模式不区分大小写匹配error/fatal/no such任何输出命中fail_pattern都会导致测试失败端口约定Postgres 映射宿主机54331PgBouncer 映射64331Shape 服务默认监听3000database_url/pooled_database_url直连与池化两条连接串分别对应DATABASE_URL与ELECTRIC_POOLED_DATABASE_URL。2. PostgreSQL 启动宏setup_pg_with_shell_name[macro setup_pg_with_shell_name shell_name container_env_vars config_opts extra_volumes] [shell $shell_name] -$fail_pattern # Create a named network for the PG container to be able to attach pooler !docker network ls | grep $pg_docker_network || docker network create $pg_docker_network !docker run \ --name $pg_container_name \ --network $pg_docker_network \ -e POSTGRES_DBelectric \ -e POSTGRES_USERpostgres \ -e POSTGRES_PASSWORDpassword \ ${container_env_vars} \ -p $pg_host_port:5432 \ -v $(realpath ../scripts/reset_wal.sh):/docker-entrypoint-initdb.d/initdb-reset_wal.sh \ ${extra_volumes} \ postgres:18-alpine \ -c wal_levellogical ${config_opts} [timeout 15] ??PostgreSQL init process complete; ready for start up. ??database system is ready to accept connections # Reset the failure pattern to avoid false failures when Electric tries to create an already # existing publication or replication slot. - [endmacro]要点解读基于postgres:18-alpine镜像强制-c wal_levellogical这是逻辑复制Electric 数据同步的基础的硬性前置条件通过命名网络electric_integration_pg_net让 PgBouncer 等附属容器与 PG 容器互通挂载reset_wal.sh作为 initdb 脚本让每个测试的 PG 实例从随机 WAL 位置起步利用 lux 的[timeout 15]??预期出现断言 PG 完成初始化最后的-空匹配用于重置fail_pattern避免 Electric 创建已存在的 publication / replication slot 时被误判为失败。基于它衍生出三个常用变体setup_pg标准参数启动setup_pg_with_ssl通过--entrypoint entrypoint-ssl.sh与挂载的 support_files/postgresql_ssl.conf、support_files/pg_hba.conf、server.crt/server.key启动带 SSL 的 PG用于secure-db-connection.luxsetup_pg_with_pooler先setup_pg再额外启动bitnamilegacy/pgbouncer:latest配置PGBOUNCER_POOL_MODEtransaction监听宿主64331。3. 生命周期与数据访问宏stop_pg/resume_pg/stop_and_remove_pg停止、恢复docker start --attach、停止并删除 PG 容器支撑postgres-disconnection.lux、replication-slot-self-conflict.lux等断连恢复类场景start_psql/seed_pg在容器内以postgres用户打开psql会话seed_pg预置items2表并批量插入 2048 行每行含约 4096 次重复的长字符串用来构造大负载测试数据teardown依次删除 pooler、PG、OTel Collector 容器并执行clean_up.sh清理_storage保证测试间互不污染。4. Electric 服务启动宏[macro setup_electric_with_env env] [invoke setup_electric_shell electric 3000 DATABASE_URL$database_url ELECTRIC_INSECUREtrue $env] [endmacro] [macro setup_electric_shell shell_name port env] [invoke start_electric_script $shell_name $port $env] [shell $shell_name] -$fail_pattern ??[notice] Starting ElectricSQL !if is_nil(System.get_env(DO_NOT_START_CONN_MAN_PING)) do \ {:ok, _pid} Electric.TestUtils.ConnectionManagerPing.start_link(manager_name: Electric.Connection.Manager.name(single_stack)) \ end [endmacro] [macro start_electric_script shell_name port env] [shell $shell_name] !ELECTRIC_PORT$port $env ../scripts/electric_dev.sh --no-color [endmacro]默认以ELECTRIC_INSECUREtrue无密钥模式启动并通过ELECTRIC_PORT指定监听端口便于多实例测试如rolling-deploy.lux中 3000/3001 双实例服务就绪的判定信号是日志中的[notice] Starting ElectricSQL启动完成后自动挂载ConnectionManagerPing探针除非设置了DO_NOT_START_CONN_MAN_PING另有setup_secure_electric secret设置ELECTRIC_INSECUREfalse与ELECTRIC_SECRET、setup_electric_with_pooler注入ELECTRIC_POOLED_DATABASE_URL等变体。5. 健康检查、租户与 Shape 请求宏[macro wait_health port expected_status] [invoke wait-for curl --silent http://localhost:$port/v1/health \{\status\:\$expected_status\\} 10 $PS1] [endmacro] [macro add_tenant tenant_id electric_port] [shell $tenant_id] !curl -X POST --silent http://localhost:$electric_port/v1/admin/database \ -H Content-Type: application/json \ -d {\database_id\:\$tenant_id\,\database_url\:\$database_url\} ??$tenant_id [endmacro] [macro shape_get_snapshot table] [invoke shape_get_url $shape_url_port table$tableoffset-1] [endmacro] [macro shape_get table handle offset] [invoke shape_get_url $shape_url_port table$tablehandle$handleoffset$offset] [endmacro] [macro shape_get_live table handle offset] [invoke shape_get_url $shape_url_port table$tablehandle$handleoffset$offsetlivetrue] [endmacro]健康检查断言GET /v1/health返回 JSON{status:active}或{status:waiting}add_tenant通过管理 APIPOST /v1/admin/database动态注册租户database_id database_urlShape 请求宏统一封装curl支持快照offset-1、增量handleoffset、实时livetrue三种模式并额外提供按端口区分的shape_get_snapshot_on/shape_get_on/shape_get_live_on用于多实例测试curl_shape宏还对响应体做jq --sort-keys排序规整消除 JSON 键序差异带来的断言抖动。五、综合案例 1安全数据库连接测试secure-db-connection.luxintegration-tests/tests/secure-db-connection.lux 完整演示了“用 SSL 强制连接 证书校验”的端到端验证覆盖四条正反向路径sslmodedisable被拒启动setup_pg_with_ssl后用?sslmodedisable的database_url启动 Electric断言出现FATAL 28000 (invalid_authorization_specification) pg_hba.conf rejects connection ... no encryption默认sslmode正常连接去掉sslmode参数后重启断言Starting replication from postgres证明 SSL 握手成功证书文件与sslmodedisable互斥同时设置ELECTRIC_DATABASE_CA_CERTIFICATE_FILE与sslmodedisable断言启动期报错When ELECTRIC_DATABASE_CA_CERTIFICATE_FILE is set, sslmode must be omitted or set to a value other than disable——这是同步服务对配置合法性的主动校验证书校验的三种结果无效证书文件[emergency] SSL connection failed to verify server certificate: Invalid CA certificate file错误根证书root_otherhost.crtCLIENT ALERT: Fatal - Unknown CA正确根证书support_files/root.crt成功输出Starting replication from postgres。配套的 integration-tests/scripts/gen_certs.sh 展示了证书生成细节根证书与服务器证书均使用prime256v1椭圆曲线密钥且服务器证书必须包含subjectAltName DNS:localhost, IP:127.0.0.1——注释明确指出OTP 26 起 Erlang:ssl不再回退到证书 CN缺少 SAN 会报{hostname_check_failed, missing_subject_altnames}。六、综合案例 2滚动部署与只读服务rolling-deploy.luxintegration-tests/tests/rolling-deploy.lux 是仓库中信息量最大的测试之一验证“滚动部署期间旧实例接管、新实例只读待命”的行为其流程可提炼为准备数据建items表并插入 10 行启动实例 13000 端口断言三阶段日志——Acquiring lock from postgres with name electric_slot_integration→Lock acquired ...→Starting replication from postgres健康检查返回active初始化 Shape客户端抓取快照保存electric-handle与electric-offset启动实例 23001 端口它只输出Acquiring lock ...而不得出现Lock acquired/Starting replication——通过-Lock acquired from postgres|Starting replication from postgres|$fail_pattern这类否定断言保证排他锁语义健康检查分别为active3000与waiting3001只读服务验证waiting状态下的实例 2 依然可响应 Shape 请求——快照返回HTTP/1.1 200 OK且electric-handle一致对不存在的表返回400 ... does not exist注释说明锁获取前 admin pool 已可用EtsInspector 可查询 DB 校验表是否存在交接handover停机实例 1 后实例 2 依次输出Lock acquired ...→Refreshing shape metadata→Starting replication from postgres健康检查翻转为active连续性验证交接前后插入的#handover test val、#post-handover test val均能在新实例上通过live拉取到且electric-handle保持不变最后用快照 0_inf日志分别做“初始数据完整”与“变更日志完整”的完备性检查。这段测试同时验证了“共享存储只读快照”实例 2 通过共享存储读取实例 1 落盘的数据轮询 5 次容忍约 1s 的磁盘刷新延迟与“租约锁 复制槽排他”两个底层机制。七、综合案例 3手动表发布manual-table-publishing.luxintegration-tests/tests/manual-table-publishing.lux 面向“数据库账号不拥有用户表”的受限环境验证ELECTRIC_MANUAL_TABLE_PUBLISHINGtrue下的行为链路用INIT_DB_SQL环境变量创建一个低权限角色low_privilege带REPLICATION属性 CREATE权限Electric 以该角色连接建表并插入数据后请求 Shape得到HTTP/1.1 503错误信息明确指出表缺失于 publicationelectric_publication_integration且ELECTRIC_MANUAL_TABLE_PUBLISHING设置阻止 Electric 自动添加ALTER PUBLICATION ... ADD TABLE items后仍 503原因变为does not have its replica identity set to FULLALTER TABLE items REPLICA IDENTITY FULL后仍 503原因变为Unable to create initial snapshot: permission denied for table itemsGRANT SELECT ON items TO low_privilege后快照成功返回HTTP/1.1 200携带electric-schema、electric-offset: 0_0以及完整的 insert 变更记录relation: [public,items]、operation: insert最后断言 schema reconciler 输出Verified publication electric_publication_integration to include [public.items] tables with REPLICA IDENTITY FULL且pg_publication_tables中该表仍保留在 publication 内。这个案例展示了集成测试的价值它把“权限不足 → 手动发布 → 复核权限 → 成功同步”这一真实运维路径完整固化任何一个环节的行为回归都会被立即捕获。八、其他场景一览故障注入、观测性与网络边界tests/目录下还有大量专项场景可按主题归类复制故障与恢复crash-recovery.lux、postgres-disconnection.lux、replication-lifetime.lux、replication-slot-self-conflict.lux、recreation-of-replication-slot.lux、self-heal-stuck-slot-creation.lux、startup-delayed-by-pending-transaction.lux、shape-suspension-resumption.lux——大量依赖stop_pg/resume_pg宏与reset_wal.sh构造的异常起点资源与性能边界exceedingly-large-transaction.lux、insufficient-connection-resources.lux、db-connection-scaledown.lux、replication-keepalive-during-backpressure.lux——其中连接资源相关用例依赖ConnectionManagerPing的输出网络与加密ipv6-to-ipv4-fallback.lux、secure-db-connection.lux、secure-mode.lux可观测性otel-export.lux通过setup_otel_collector宏启动 support_files/otel-collector-config.yaml 描述的otel/opentelemetry-collector-contrib容器并用grep_otel_collector_output宏docker logs support_files/normalize-otel-output.py 归一化 grep断言遥测数据部署形态rolling-deploy.lux只读滚动部署、pooled-connections.luxPgBouncer 池化连接、manual-table-publishing.lux受限账号、invalid-wal-level.lux/invalidated-replication-slot.lux/not-owner-of-the-publication.lux/not-owner-of-the-table.lux非法前置条件的负面路径。每个.lux文件都以[doc ...]头注释描述验证目标并以[cleanup] [invoke teardown]收尾保证失败时也能回收容器与存储。九、从这套测试体系中可以复用什么即便你不使用 lux这套设计的思路也具备直接参考价值固定版本的测试驱动工具Makefile锁定 lux 的 commit并用TIMEOUT_MULTIPLIER提供超时弹性兼顾可复现性与慢环境适配宏化的组件编排原语把“起 PG、起服务、健康检查、拉 Shape”抽象成可组合宏测试文件只写业务断言基础设施细节收敛在 integration-tests/tests/_macros.luxinc 一处正向与负面路径成对设计同一个能力SSL、手动发布、滚动部署既验证成功路径也验证被拒、报错、等待等失败路径错误信息本身成为断言的一部分确定性输入 可观测探针reset_wal.sh保证每次从随机但合法的 WAL 起点开始ConnectionManagerPing把“连接管理器是否卡顿”变成可断言的日志让隐性的性能问题显性化干净的清理契约统一teardown宏 clean_up.sh确保集成测试可以反复运行而不污染宿主机状态。十、快速上手检查清单步骤命令 / 文件说明准备环境make克隆并编译 luxintegration-tests/Makefile运行全部测试./run.sh默认执行tests/*.luxintegration-tests/run.sh运行单个测试./run.sh tests/rolling-deploy.lux定向调试单个场景放大超时TIMEOUT_MULTIPLIER2000 ./run.sh慢速环境防误报前置条件Docker 可用、能拉取postgres:18-alpine/bitnamilegacy/pgbouncer/otel/opentelemetry-collector-contrib镜像宏中使用的容器依赖清理teardown宏自动执行 integration-tests/scripts/clean_up.sh失败时也会触发需要说明的边界本套测试面向仓库内部同步服务的开发验证运行前提是具备可用的 Docker 环境与网络拉取权限若需复用到自己的项目建议保留宏抽象与超时倍增器的设计并根据自身服务端口与镜像调整_macros.luxinc中的全局变量。【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表