跳转到正文

测试策略

merql 使用由预言结果驱动的测试模型。每个测试场景都会定义一组已知输入(三种数据库状态)、一个确定的预期结果(预言结果),并将实际合并输出与该预期结果进行比较。32 个场景涵盖无冲突合并、冲突、标识解析、数据类型、规模和边界情况。

命令行
# Run the full verification suite.
./bin/verify-all

本页中的命令需要在 merql 仓库检出后运行 composer install。bin/ 中的开发脚本、scenarios/ 语料库和测试不属于 Composer 软件包。开发依赖包括 PHPUnit 13,该版本需要 PHP 8.4 或更高版本,尽管该库本身可在 PHP 8.2 上运行。

由预言结果驱动的测试

目前没有可执行数据库三方合并的参考工具,因此预言结果是根据已知输入计算得出的,而不是由外部工具生成。每个场景都有确定的预期结果,可通过逻辑验证:

  1. 设置 -- 创建三个内容已知的数据库状态(base、ours、theirs)。
  2. 预言结果 -- 根据已知输入计算预期合并结果。
  3. 实际结果 -- 对这三个状态运行 merql 合并。
  4. 比较 -- 实际结果必须与预期结果完全一致。
代码
Known inputs (base, ours, theirs)
         |                    |
    Deterministic          merql merge
    computation
         |                    |
    Expected result       Actual result
         |                    |
         -----> Compare <-----
                  |
            Pass or Fail

运行测试

完整验证

verify-all 脚本是最终检查关卡。它会运行静态分析、编码规范检查、PHPUnit 测试和预言结果回归测试套件:

命令行
./bin/verify-all

在任何工作被视为完成之前,此检查都必须通过。

预言结果回归测试套件

运行全部 32 个场景:

命令行
./bin/test-regression

运行单个类别:

命令行
./bin/test-regression --category clean
./bin/test-regression --category conflict
./bin/test-regression --category identity

类别对应 scenarios/ 下的目录名称:clean、conflict、 identity、types、scale 和 edge。未知类别会显示 No scenarios found 并成功退出。可以重复使用 --category 来运行 多个类别。

脚本也接受 --jobs,但会忽略其值,并按顺序逐个运行场景(#5)。

以最少输出运行(仅通过/失败):

命令行
./bin/test-regression --fast

单个场景

通过完整流程测试一个场景:

命令行
./bin/test-scenario column-level-clean

单独运行预言结果步骤

命令行
./bin/oracle column-level-clean    # Compute expected result
./bin/actual column-level-clean    # Run merql, capture output
./bin/compare column-level-clean   # Diff oracle vs actual

单元测试

命令行
composer test:unit         # Isolated component tests
composer test:integration  # Integration tests (SQLite, plus MySQL when configured)
composer test              # All PHPUnit suites
composer test:oracle       # Oracle regression corpus (bin/test-regression)
composer verify            # analyse + cs + test + test:oracle

composer test 只运行 PHPUnit。oracle PHPUnit 套件指向 tests/Oracle,其中包含场景运行器类,但没有测试用例,因此这 32 个 场景只会通过 composer test:oracle、composer verify 或 ./bin/verify-all 运行。

除非 MERQL_MYSQL_HOST、MERQL_MYSQL_DATABASE 和 MERQL_MYSQL_USER(以及可选的 MERQL_MYSQL_PASSWORD 和 MERQL_MYSQL_PORT)均指向测试数据库,否则会跳过 MySQL 集成测试。

代码质量

命令行
composer cs            # PSR-12 coding standards check
composer cs:fix        # Auto-fix coding standards
composer analyse       # PHPStan level 10 static analysis

场景类别

clean(8 个场景)

无冲突的干净合并。双方都可以更改数据,但绝不会更改同一行的同一列。

场景 测试内容
insert-only-theirs 对方插入行,我们这边未更改。
update-only-theirs 对方更新行,我们这边未更改。
delete-only-theirs 对方删除行,我们这边未更改。
insert-only-ours 我们这边插入行,对方未更改。
mixed-no-overlap 双方更改了不同的表或行。
both-same-change 双方进行了相同的更改(无冲突)。
column-level-clean 双方更改了同一行的不同列。
multi-table 跨多个表的更改。

conflict(6 个场景)

必须产生冲突的场景。

场景 测试内容
both-update-same-column 双方将同一列更改为不同的值。
update-vs-delete 一方更新某行,另一方删除同一行。
delete-vs-update 更新与删除的反向情况。
both-insert-same-pk 双方都插入了主键相同的行。
multiple-conflicts 一次合并中出现多个冲突。
partial-conflict 部分列存在冲突,其他列可无冲突合并。

identity(4 个场景)

行标识边界情况。

场景 测试内容
auto-increment 新行在各分支中具有不同的 ID。
natural-key 使用唯一列而不是 PK 进行匹配。
composite-key 多列主键。
no-key 没有主键的表(回退到内容哈希)。

types(6 个场景)

涵盖所有列类型的数据类型处理。

场景 测试内容
text-columns VARCHAR、TEXT、LONGTEXT 值。
numeric-columns INT、DECIMAL、FLOAT 值。
date-columns DATE、DATETIME、TIMESTAMP 值。
json-columns JSON 列合并。
blob-columns 二进制数据。
null-handling NULL 到值、值到 NULL 的转换。

scale(4 个场景)

负载下的性能。

场景 测试内容
1k-rows 1,000 行。
10k-rows 10,000 行。
100k-rows 100,000 行。
wide-table 含 50+ 列的表。

edge(4 个场景)

边界情况和边界条件。

场景 测试内容
empty-changeset 一侧或双方都没有更改。
schema-mismatch 快照之间添加或删除列。
encoding UTF-8、表情符号、特殊字符。
large-text 超大的 TEXT/LONGTEXT 值。

场景结构

每个场景都是 scenarios/<category>/<name>/ 下的一个目录,包含 scenario.json 配置:

JSON
{
    "name": "column-level-clean",
    "category": "clean",
    "description": "Both sides modify same row but different columns",
    "tables": ["test_posts"],
    "expectations": {
        "conflicts": 0,
        "operations": 3,
        "changeset_match": "exact",
        "merge_match": "exact",
        "sql_match": "semantic"
    }
}

对于冲突场景,预期冲突会详细列出:

JSON
{
    "expectations": {
        "conflicts": 1,
        "conflict_details": [
            {
                "table": "test_posts",
                "primary_key": {"id": 42},
                "column": "title",
                "ours_value": "Welcome",
                "theirs_value": "Greetings"
            }
        ]
    }
}

ScenarioRunner 流程

ScenarioRunner::run() 负责协调单个场景:

  1. OracleCapture::compute() 根据场景的 JSON 数据构建三个快照并运行合并。
  2. ScenarioComparator::compare() 根据 scenario.json 中的预期值检查合并结果。
  3. 返回通过/失败状态以及失败消息列表。
PHP
use Merql\Tests\Oracle\ScenarioRunner;

$result = ScenarioRunner::run($scenario);
// ['name' => 'column-level-clean', 'pass' => true, 'failures' => []]

ScenarioRunner::runAll() 按顺序运行每个场景:

PHP
$results = ScenarioRunner::runAll($scenarios);

添加新场景

  1. 创建目录:scenarios/<category>/<name>/。
  2. 创建 scenario.json,填写场景元数据和预期结果。
  3. 在 JSON 配置中定义三种数据库状态:base 数据、ours 的更改、theirs 的更改。
  4. 运行场景:./bin/test-scenario <name>。
  5. 验证完整测试套件:./bin/verify-all。

代码质量标准

merql 执行以下检查:

  • PHPStan 10 级 -- 严格静态分析,不设基线例外。
  • PSR-12 -- 由 phpcs 检查编码规范。
  • 预言结果回归测试 -- 32 个场景必须全部通过。
  • 单元测试 -- 针对各个组件的独立测试。

verify-all 脚本会依次运行全部四项检查。不接受只通过部分检查。

持续集成

CI 会在每次向 main 推送或提交拉取请求时,运行编码规范检查、PHPStan、composer test、针对 MySQL 8.4 的 mysqli 集成测试套件、合并原语覆盖率检查,以及 composer audit。它不会运行预言结果回归语料库(#6),因此在依赖某项更改之前,请在本地运行 composer test:oracle 或 ./bin/verify-all。