Hotdry.
systems-engineering

使用 RegreSQL 实现 PostgreSQL 查询回归测试

在 CI/CD 管道中引入 RegreSQL,进行 PostgreSQL 查询的自动化回归测试,涵盖 schema 差异检测、执行计划比较和结果验证,确保数据库演进中的稳定性。

在现代软件开发中,数据库查询的稳定性至关重要。每次 schema 修改、索引优化或应用逻辑调整都可能引入意外的回归,导致生产环境中的性能下降或数据不一致。传统的集成测试往往忽略了查询层面的细粒度验证,而 RegreSQL 作为一款专为 PostgreSQL 设计的开源工具,正好填补了这一空白。它通过自动化回归测试,结合 schema 差异分析、执行计划比较和结果验证,帮助开发者在 CI/CD 管道中提前捕捉问题,确保查询在数据库演进过程中保持可靠。

RegreSQL 的核心理念源于 PostgreSQL 项目自身的回归测试框架,将这一成熟方法论扩展到应用查询层面。不同于 ORM 抽象 SQL 的方式,RegreSQL 拥抱 SQL 的字符串本质,直接测试查询的正确性和性能。通过扫描目录中的 SQL 文件,它执行每个命名查询,并与预期结果比较。如果 schema 变化导致计划变化或结果偏差,测试将立即失败。同时,它还跟踪性能基线,检测如顺序扫描等常见问题,提供预警以避免生产事故。

要落地 RegreSQL,首先需要安装工具。由于它是 Go 语言编写,使用以下命令即可快速安装:

go install github.com/boringSQL/regresql@latest

确保 Go 环境已配置好 PATH。接下来,配置 PostgreSQL 连接字符串,例如在环境变量中设置 REGRESQL_DSN=postgres://user:pass@localhost/dbname?sslmode=disable。测试数据库应使用固定数据集,避免生产数据干扰。推荐使用 Docker 容器化测试环境,如:

docker run --name test-db -e POSTGRES_PASSWORD=pass -p 5432:5432 -d postgres:15

初始化 RegreSQL 项目:在查询目录下运行 regresql init,它会创建 regresql/ 子目录,包括 plans/ 和 baselines/。将 SQL 文件置于根目录,例如 queries/album.sql

-- name: list-albums-by-artist
-- List albums for a given artist
SELECT album.title, sum(track.milliseconds) * INTERVAL '1 ms' AS duration
FROM album
JOIN artist USING (artistid)
LEFT JOIN track USING (albumid)
WHERE artist.name = :name
GROUP BY album.title
ORDER BY album.title;

为每个查询创建计划文件 regresql/plans/album.yaml,定义参数和固定装置:

fixtures:
  - basic_artists

"1":
  name: "Red Hot Chili Peppers"

"2":
  name: "The Beatles"

固定装置在 regresql/fixtures/basic_artists.yaml 中定义,支持 YAML 数据或生成器:

fixture: basic_artists
description: Basic artist data for testing
cleanup: rollback

data:
  - table: artist
    rows:
      - artistid: 1
        name: "Red Hot Chili Peppers"
      - artistid: 2
        name: "The Beatles"

对于大规模测试,使用生成器创建真实数据:

fixture: large_dataset
generate:
  - table: tracks
    count: 10000
    columns:
      trackid:
        generator: sequence
        start: 1
      name:
        generator: string
        length: 50
      milliseconds:
        generator: int
        min: 1000
        max: 300000

运行 regresql baseline 生成初始性能基线,包括执行成本和时间阈值(默认允许 10% 浮动)。然后,使用 regresql test 执行测试:

regresql test -C /path/to/queries

输出将显示每个查询的结果验证和性能检查,例如检测到顺序扫描时发出警告:

⚠️ Sequential scan detected on table 'artist'
Suggestion: Consider adding an index on artist.name

要集成 schema 差异检测,RegreSQL 通过比较执行计划(EXPLAIN ANALYZE 输出)实现。计划文件可包含 -- regresql: plan-compare 标记,测试时自动 diff 计划树。如果 schema 变化导致计划从索引扫描退化为顺序扫描,测试失败。结果验证使用 JSON 格式比较行集,支持浮点容差参数 -- regresql: difffloattolerance=0.05 以处理数值精度问题。

在 CI/CD 管道中的落地至关重要。以 GitHub Actions 为例,创建一个 workflow 文件 .github/workflows/regresql.yml

name: RegreSQL Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: pass
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: go install github.com/boringSQL/regresql@latest
      - run: |
          # 初始化测试 DB
          psql -h localhost -U postgres -c "CREATE DATABASE testdb;"
          # 加载 schema 和固定装置
          psql -h localhost -U postgres -d testdb -f schema.sql
          # 运行 RegreSQL
          REGRESQL_DSN="postgres://postgres:pass@localhost/testdb" regresql test
        env:
          PGPASSWORD: pass

此管道在每次提交时自动运行测试。如果失败,CI 阻塞合并。监控点包括:测试覆盖率(使用 regresql list 统计 SQL 文件)、性能阈值警报(自定义 >20% 回归失败)、计划变化日志(输出到 artifacts)。

参数配置建议:默认 10% 性能容差适用于大多数场景;对于高负载查询,调整为 5% 并启用 nobaseline 跳过非关键测试。回滚策略:在 CI 失败时,回滚到上个基线 commit,使用 regresql update 手动验证修复后更新预期。

RegreSQL 还支持输出格式如 JUnit 或 JSON,便于集成到 Jenkins 或其他工具。风险包括测试数据不一致导致假阳性,建议使用事务回滚(cleanup: rollback)和隔离固定装置。总体而言,通过这些可落地步骤,RegreSQL 显著提升了 PostgreSQL 查询的可靠性,减少了生产事故。

资料来源:boringSQL 博客(https://boringsql.com/posts/regresql-testing-queries/)、RegreSQL GitHub 仓库(https://github.com/boringSQL/regresql)。

查看归档